Skip to content

Commit

Permalink
Merge pull request #8 from VladIacobIonut/SPM
Browse files Browse the repository at this point in the history
Add SPM
  • Loading branch information
VladIacobIonut authored May 8, 2020
2 parents 4f8e215 + 39580e2 commit 2564637
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "Swinflate",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "Swinflate",
targets: ["Swinflate"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "Swinflate",
dependencies: []),
.testTarget(
name: "SwinflateTests",
dependencies: ["Swinflate"]),
]
)
112 changes: 112 additions & 0 deletions Sources/Swinflate/SWHorizontalStackLayout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// SWHorizontalStackLayout.swift
// Swinflate
//
// Created by Vlad on 12/20/18.
// Copyright © 2018 Vlad Iacob. All rights reserved.
//

import UIKit

public final class SWHorizontalStackLayout: UICollectionViewFlowLayout {
// MARK: - Properties

public var hasStackEffect = false
public var isPagingEnabled = true
private var firstSetupDone = false
private var cellWidth: CGFloat = 0
private let scaleRatio: CGFloat = 0.05

// MARK: - Override

public override func prepare() {
super.prepare()

guard firstSetupDone else {
setup()
firstSetupDone = true
return
}
}

public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
true
}

public override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// If the property `isPagingEnabled` is set to false, we don't enable paging and thus return the current contentoffset.
guard isPagingEnabled else {
let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
return latestOffset
}

// Page width used for estimating and calculating paging.
let pageWidth = cellWidth + self.minimumInteritemSpacing

// Make an estimation of the current page position.
let approximatePage = self.collectionView!.contentOffset.x / pageWidth

// Determine the current page based on velocity.
let currentPage = (velocity.x < 0.0) ? floor(approximatePage) : ceil(approximatePage)

// Create custom flickVelocity.
let flickVelocity = velocity.x * 0.4

// Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)

// Calculate newVerticalOffset.
let newVerticalOffset = ((currentPage + flickedPages) * pageWidth) - self.collectionView!.contentInset.left

return CGPoint(x: newVerticalOffset, y: proposedContentOffset.y)
}

public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let allAttributes = super.layoutAttributesForElements(in: rect) else {
return nil
}

guard let firstAttribute = allAttributes.first else { return nil }

cellWidth = firstAttribute.size.width

for attribute in allAttributes {
self.updateAttribute(attribute)
}

return allAttributes
}

// MARK: - Private Functions

private func setup() {
scrollDirection = .horizontal
collectionView!.decelerationRate = UIScrollView.DecelerationRate.normal
}

private func updateAttribute(_ attributes: UICollectionViewLayoutAttributes) {
guard let collectionView = collectionView else { return }
let minX = collectionView.bounds.minX + collectionView.contentInset.left
let maxX = attributes.frame.origin.x

let finalX = max(minX, maxX)
var origin = attributes.frame.origin
let deltaY = (finalX - origin.x) / attributes.frame.width

let scale = 1 - deltaY * scaleRatio
let translation = CGFloat((attributes.zIndex + 1) * 10)

var t = CGAffineTransform.identity
t = t.scaledBy(x: 1, y: scale)

if hasStackEffect {
t = t.translatedBy(x: -(translation + deltaY * translation), y: 0)
}
attributes.alpha = 1 - deltaY * 0.6
attributes.transform = t

origin.x = finalX
attributes.frame = CGRect(origin: origin, size: attributes.frame.size)
attributes.zIndex = attributes.indexPath.row
}
}
110 changes: 110 additions & 0 deletions Sources/Swinflate/SWInflateLayout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//
// SWInflateLayout.swift
// Swinflate
//
// Created by Vlad on 12/17/18.
// Copyright © 2018 Vlad Iacob. All rights reserved.
//

import UIKit

public final class SWInflateLayout: UICollectionViewFlowLayout {
// MARK: - Properties

public var isPagingEnabled = true
public var leftContentOffset: CGFloat = 0
private var firstSetupDone = false
private var cellWidth: CGFloat = 0
private var contentSpacing: CGFloat = 0

// MARK: - Override

override public func prepare() {
super.prepare()

guard !firstSetupDone else {
return
}

scrollDirection = .horizontal
firstSetupDone = true
}

override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
true
}

public override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// If the property `isPagingEnabled` is set to false, we don't enable paging and thus return the current contentoffset.
guard isPagingEnabled else {
let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
return latestOffset
}

// Page width used for estimating and calculating paging
let pageWidth = cellWidth + self.minimumInteritemSpacing

// Make an estimation of the current page position.
let approximatePage = self.collectionView!.contentOffset.x / pageWidth

// Determine the current page based on velocity.
let currentPage = (velocity.x < 0.0) ? floor(approximatePage) : ceil(approximatePage)

// Create custom flickVelocity.
let flickVelocity = velocity.x * 0.4

// Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)

// Calculate newHorizontalOffset
let newHorizontalOffset = ((currentPage + flickedPages) * pageWidth) - self.collectionView!.contentInset.left

return CGPoint(x: newHorizontalOffset - (2 * leftContentOffset), y: proposedContentOffset.x)
}

override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let items = NSMutableArray (array: super.layoutAttributesForElements(in: rect)!, copyItems: true)

guard let firstCellAttribute = items.firstObject as? UICollectionViewLayoutAttributes else {
return nil
}

cellWidth = firstCellAttribute.size.width

guard let collectionViewBounds = collectionView?.bounds else {
return nil
}

contentSpacing = (collectionViewBounds.width - cellWidth) / 2 - leftContentOffset
collectionView?.contentInset = UIEdgeInsets(top: collectionView?.contentInset.top ?? 0, left: contentSpacing - leftContentOffset, bottom: collectionView?.contentInset.bottom ?? 0 , right: 15)

items.enumerateObjects { (object, index, stop) in
let attribute = object as! UICollectionViewLayoutAttributes
self.cellWidth = attribute.size.width
self.updateCellAttributes(attribute: attribute)
}

return items as? [UICollectionViewLayoutAttributes]
}

// MARK: - Private functions

private func updateCellAttributes(attribute: UICollectionViewLayoutAttributes) {
var finalX: CGFloat = attribute.frame.midX - (collectionView?.contentOffset.x)!
let centerX = attribute.frame.midX - (collectionView?.contentOffset.x)!
if centerX < collectionView!.frame.midX - contentSpacing {
finalX = max(centerX, collectionView!.frame.minX)
}
else if centerX > collectionView!.frame.midX + contentSpacing {
finalX = min(centerX, collectionView!.frame.maxX)
}

let deltaY = abs(finalX - collectionView!.frame.midX) / attribute.frame.width
let scale = 1 - deltaY * 0.2
let alpha = 1 - deltaY

attribute.alpha = alpha
attribute.transform = CGAffineTransform(scaleX: 1, y: scale)
}
}

5 changes: 5 additions & 0 deletions Swinflate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Swinflate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Swinflate

A description of this package.
Binary file not shown.
7 changes: 7 additions & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import XCTest

import SwinflateTests

var tests = [XCTestCaseEntry]()
tests += SwinflateTests.allTests()
XCTMain(tests)
9 changes: 9 additions & 0 deletions Tests/SwinflateTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import XCTest

#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(SwinflateTests.allTests),
]
}
#endif

0 comments on commit 2564637

Please sign in to comment.