From d539aee9f8a7fff0913a3a2b41d51a41c1811db9 Mon Sep 17 00:00:00 2001 From: Jin Wang Date: Wed, 22 Feb 2017 14:11:18 +1100 Subject: [PATCH] + Removed position property from the animator delegate method and put it into PagerCollectionViewLayoutAttributes. + Added scrollDirection property to support vertical scrolling. + Added extra properties to support more animation options. --- Source/AnimatedCollectionViewLayout.swift | 60 ++++++++++++++++------ Source/CrossFadeAttributesAnimator.swift | 3 +- Source/CubeAttributesAnimator.swift | 3 +- Source/LayoutAttributesAnimator.swift | 2 +- Source/LinearCardAttributesAnimator.swift | 3 +- Source/PageAttributesAnimator.swift | 3 +- Source/ParallaxAttributesAnimator.swift | 27 ++++++++-- Source/RotateInOutAttributesAnimator.swift | 3 +- Source/TurnAttributesAnimator.swift | 3 +- Source/ZoomInOutAttributesAnimator.swift | 3 +- 10 files changed, 82 insertions(+), 28 deletions(-) diff --git a/Source/AnimatedCollectionViewLayout.swift b/Source/AnimatedCollectionViewLayout.swift index 387ce02..11e0fc9 100644 --- a/Source/AnimatedCollectionViewLayout.swift +++ b/Source/AnimatedCollectionViewLayout.swift @@ -20,36 +20,53 @@ public class AnimatedCollectionViewLayout: UICollectionViewFlowLayout { public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil } - return attributes.flatMap { $0.copy() as? UICollectionViewLayoutAttributes }.map { self.transformLayoutAttributes($0) } + return attributes.flatMap { $0.copy() as? PagerCollectionViewLayoutAttributes }.map { self.transformLayoutAttributes($0) } } - override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { // We have to return true here so that the layout attributes would be recalculated // everytime we scroll the collection view. return true } - private func transformLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + private func transformLayoutAttributes(_ attributes: PagerCollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { - guard let collectionView = self.collectionView, - let a = attributes as? PagerCollectionViewLayoutAttributes else { return attributes } + guard let collectionView = self.collectionView else { return attributes } - // The position for each cell is defined as the ratio of the distance between - // the center of the cell and the center of the screen and the screen width. - // It can be negative if the cell is, for instance, on the left of the screen. + let a = attributes - let width = collectionView.frame.width - let centerX = width / 2 - let offset = collectionView.contentOffset.x - let itemX = a.center.x - offset - let position = (itemX - centerX) / width + /** + The position for each cell is defined as the ratio of the distance between + the center of the cell and the center of the collectionView and the collectionView width/height + depending on the scroll direction. It can be negative if the cell is, for instance, + on the left of the screen if you're scrolling horizontally. + */ + + let distance: CGFloat + let itemOffset: CGFloat + + if scrollDirection == .horizontal { + distance = collectionView.frame.width + itemOffset = a.center.x - collectionView.contentOffset.x + a.startOffset = (a.frame.origin.x - collectionView.contentOffset.x) / a.frame.width + a.endOffset = (a.frame.origin.x - collectionView.contentOffset.x - collectionView.frame.width) / a.frame.width + } else { + distance = collectionView.frame.height + itemOffset = a.center.y - collectionView.contentOffset.y + a.startOffset = (a.frame.origin.y - collectionView.contentOffset.y) / a.frame.height + a.endOffset = (a.frame.origin.y - collectionView.contentOffset.y - collectionView.frame.height) / a.frame.height + } + + a.scrollDirection = scrollDirection + a.middleOffset = itemOffset / distance - 0.5 // Cache the contentView since we're going to use it a lot. - if a.contentView == nil { - a.contentView = collectionView.cellForItem(at: attributes.indexPath)?.contentView + if a.contentView == nil, + let c = collectionView.cellForItem(at: attributes.indexPath)?.contentView { + a.contentView = c } - animator?.animate(collectionView: collectionView, attributes: a, position: position) + animator?.animate(collectionView: collectionView, attributes: a) return a } @@ -58,10 +75,21 @@ public class AnimatedCollectionViewLayout: UICollectionViewFlowLayout { /// A custom layout attributes that contains extra information. public class PagerCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes { public var contentView: UIView? + public var scrollDirection: UICollectionViewScrollDirection = .vertical + public var originFrame: CGRect = .zero + + public var startOffset: CGFloat = 0 + public var middleOffset: CGFloat = 0 + public var endOffset: CGFloat = 0 public override func copy(with zone: NSZone? = nil) -> Any { let copy = super.copy(with: zone) as! PagerCollectionViewLayoutAttributes copy.contentView = contentView + copy.scrollDirection = scrollDirection + copy.originFrame = originFrame + copy.startOffset = startOffset + copy.middleOffset = middleOffset + copy.endOffset = endOffset return copy } } diff --git a/Source/CrossFadeAttributesAnimator.swift b/Source/CrossFadeAttributesAnimator.swift index e2d3cd7..1764b37 100644 --- a/Source/CrossFadeAttributesAnimator.swift +++ b/Source/CrossFadeAttributesAnimator.swift @@ -13,7 +13,8 @@ import UIKit public struct CrossFadeAttributeAnimator: LayoutAttributesAnimator { public init() {} - public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes, position: CGFloat) { + public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes) { + let position = attributes.middleOffset let contentOffset = collectionView.contentOffset attributes.frame = CGRect(origin: contentOffset, size: attributes.frame.size) attributes.alpha = 1 - abs(position) diff --git a/Source/CubeAttributesAnimator.swift b/Source/CubeAttributesAnimator.swift index bebcadf..d817365 100644 --- a/Source/CubeAttributesAnimator.swift +++ b/Source/CubeAttributesAnimator.swift @@ -22,7 +22,8 @@ public struct CubeAttributeAnimator: LayoutAttributesAnimator { self.totalAngle = totalAngle } - public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes, position: CGFloat) { + public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes) { + let position = attributes.middleOffset if abs(position) >= 1 { attributes.contentView?.layer.transform = CATransform3DIdentity } else { diff --git a/Source/LayoutAttributesAnimator.swift b/Source/LayoutAttributesAnimator.swift index cbcca84..b780ea1 100644 --- a/Source/LayoutAttributesAnimator.swift +++ b/Source/LayoutAttributesAnimator.swift @@ -9,5 +9,5 @@ import UIKit public protocol LayoutAttributesAnimator { - func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes, position: CGFloat) + func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes) } diff --git a/Source/LinearCardAttributesAnimator.swift b/Source/LinearCardAttributesAnimator.swift index 288f3fa..755175a 100644 --- a/Source/LinearCardAttributesAnimator.swift +++ b/Source/LinearCardAttributesAnimator.swift @@ -28,7 +28,8 @@ public struct LinearCardAttributeAnimator: LayoutAttributesAnimator { self.scaleRate = scaleRate } - public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes, position: CGFloat) { + public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes) { + let position = attributes.middleOffset let width = collectionView.frame.width let transitionX = -(width * itemSpacing * position) let scaleFactor = scaleRate - 0.1 * abs(position) diff --git a/Source/PageAttributesAnimator.swift b/Source/PageAttributesAnimator.swift index 526f976..508a558 100644 --- a/Source/PageAttributesAnimator.swift +++ b/Source/PageAttributesAnimator.swift @@ -17,7 +17,8 @@ public struct PageAttributeAnimator: LayoutAttributesAnimator { self.scaleRate = scaleRate } - public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes, position: CGFloat) { + public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes) { + let position = attributes.middleOffset let contentOffset = collectionView.contentOffset let itemOrigin = attributes.frame.origin let scaleFactor = scaleRate * min(position, 0) + 1.0 diff --git a/Source/ParallaxAttributesAnimator.swift b/Source/ParallaxAttributesAnimator.swift index 2514ead..51d7dc2 100644 --- a/Source/ParallaxAttributesAnimator.swift +++ b/Source/ParallaxAttributesAnimator.swift @@ -19,14 +19,33 @@ public struct ParallaxAttributesAnimator: LayoutAttributesAnimator { self.speed = speed } - public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes, position: CGFloat) { + public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes) { + let position = attributes.middleOffset + let direction = attributes.scrollDirection + + guard let contentView = attributes.contentView else { return } + if abs(position) >= 1 { // Reset views that are invisible. - attributes.contentView?.transform = .identity - } else { + contentView.transform = .identity + } else if direction == .horizontal { let width = collectionView.frame.width let transitionX = -(width * speed * position) - attributes.contentView?.transform = CGAffineTransform(translationX: transitionX, y: 0) + let transform = CGAffineTransform(translationX: transitionX, y: 0) + let newFrame = attributes.bounds.applying(transform) + + contentView.frame = newFrame + } else { + let height = collectionView.frame.height + let transitionY = -(height * speed * position) + let transform = CGAffineTransform(translationX: 0, y: transitionY) + + // By default, the content view takes all space in the cell + let newFrame = attributes.bounds.applying(transform) + + // We don't use transform here since there's an issue if layoutSubviews is called + // for every cell due to layout changes in binding method. + contentView.frame = newFrame } } } diff --git a/Source/RotateInOutAttributesAnimator.swift b/Source/RotateInOutAttributesAnimator.swift index e480a33..e3663ea 100644 --- a/Source/RotateInOutAttributesAnimator.swift +++ b/Source/RotateInOutAttributesAnimator.swift @@ -25,7 +25,8 @@ public struct RotateInOutAttributesAnimator: LayoutAttributesAnimator { self.maxRotate = maxRotate } - public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes, position: CGFloat) { + public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes) { + let position = attributes.middleOffset if abs(position) >= 1 { attributes.contentView?.transform = .identity attributes.alpha = 1.0 diff --git a/Source/TurnAttributesAnimator.swift b/Source/TurnAttributesAnimator.swift index 65f14ab..0ba986d 100644 --- a/Source/TurnAttributesAnimator.swift +++ b/Source/TurnAttributesAnimator.swift @@ -15,7 +15,8 @@ public struct TurnAttributeAnimator: LayoutAttributesAnimator { self.perspective = perspective } - public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes, position: CGFloat) { + public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes) { + let position = attributes.middleOffset if abs(position) >= 1 { attributes.contentView?.layer.transform = CATransform3DIdentity } else { diff --git a/Source/ZoomInOutAttributesAnimator.swift b/Source/ZoomInOutAttributesAnimator.swift index 0454378..493e0e0 100644 --- a/Source/ZoomInOutAttributesAnimator.swift +++ b/Source/ZoomInOutAttributesAnimator.swift @@ -18,7 +18,8 @@ public struct ZoomInOutAttributesAnimator: LayoutAttributesAnimator { self.scaleRate = scaleRate } - public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes, position: CGFloat) { + public func animate(collectionView: UICollectionView, attributes: PagerCollectionViewLayoutAttributes) { + let position = attributes.middleOffset if abs(position) >= 1 { attributes.contentView?.transform = .identity } else {