From 6aac568b3a45a8801363ec7f5ec24963aaaca5b7 Mon Sep 17 00:00:00 2001 From: Lukas Carvajal Date: Thu, 13 May 2021 01:05:12 +0200 Subject: [PATCH 1/7] Add support for .top snapping for vertical scrolling --- .../Classes/SnappingLayout.swift | 130 +++++++++++++----- 1 file changed, 96 insertions(+), 34 deletions(-) diff --git a/Sources/SnappingLayout/Classes/SnappingLayout.swift b/Sources/SnappingLayout/Classes/SnappingLayout.swift index 9521346..e66a617 100644 --- a/Sources/SnappingLayout/Classes/SnappingLayout.swift +++ b/Sources/SnappingLayout/Classes/SnappingLayout.swift @@ -13,6 +13,7 @@ public class SnappingLayout: UICollectionViewFlowLayout { case left case center case right + case top } // MARK: - Properties @@ -29,52 +30,113 @@ public class SnappingLayout: UICollectionViewFlowLayout { guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) } - + var offsetAdjusment = CGFloat.greatestFiniteMagnitude let horizontalPosition: CGFloat - - switch snapPosition { - case .left: - horizontalPosition = proposedContentOffset.x + collectionView.contentInset.left + sectionInset.left - case .center: - horizontalPosition = proposedContentOffset.x + (collectionView.bounds.width * 0.5) - case .right: - horizontalPosition = proposedContentOffset.x + collectionView.bounds.width - sectionInset.right - } - - let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height) - let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect) - layoutAttributesArray?.forEach { layoutAttributes in - let itemHorizontalPosition: CGFloat - + let verticalPosition: CGFloat + + switch scrollDirection { + case .horizontal: switch snapPosition { case .left: - itemHorizontalPosition = layoutAttributes.frame.minX - collectionView.contentInset.left + horizontalPosition = proposedContentOffset.x + collectionView.contentInset.left + sectionInset.left + verticalPosition = proposedContentOffset.y case .center: - itemHorizontalPosition = layoutAttributes.center.x + horizontalPosition = proposedContentOffset.x + (collectionView.bounds.width * 0.5) + verticalPosition = proposedContentOffset.y case .right: - itemHorizontalPosition = layoutAttributes.frame.maxX + collectionView.contentInset.right + horizontalPosition = proposedContentOffset.x + collectionView.bounds.width - sectionInset.right + verticalPosition = proposedContentOffset.y + case .top: + horizontalPosition = proposedContentOffset.x + verticalPosition = proposedContentOffset.y } - - if abs(itemHorizontalPosition - horizontalPosition) < abs(offsetAdjusment) { - // If the drag velocity is lower than the minimum velocity (no matter the direction): - // snap the current cell to it's original position. - if abs(velocity.x) < self.minimumSnapVelocity { - offsetAdjusment = itemHorizontalPosition - horizontalPosition + case .vertical: + switch snapPosition { + case .left, .right: + horizontalPosition = proposedContentOffset.x + verticalPosition = proposedContentOffset.y + case .center: + horizontalPosition = proposedContentOffset.x + verticalPosition = proposedContentOffset.y + case .top: + horizontalPosition = proposedContentOffset.x + verticalPosition = proposedContentOffset.y + collectionView.bounds.height - sectionInset.top + } + } + + let targetRect = CGRect(x: proposedContentOffset.x, y: proposedContentOffset.y, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height) + let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect) + layoutAttributesArray?.forEach { layoutAttributes in + + switch scrollDirection { + case .horizontal: + let itemHorizontalPosition: CGFloat + + switch snapPosition { + case .left: + itemHorizontalPosition = layoutAttributes.frame.minX - collectionView.contentInset.left + case .center: + itemHorizontalPosition = layoutAttributes.center.x + case .right: + itemHorizontalPosition = layoutAttributes.frame.maxX + collectionView.contentInset.right + case .top: + itemHorizontalPosition = horizontalPosition } - // If the velocity is higher than the snap threshold and drag is right->left: - // move to the next cell on the right. - else if velocity.x > 0 { - offsetAdjusment = itemHorizontalPosition - horizontalPosition + (layoutAttributes.bounds.width + self.minimumLineSpacing) + + if abs(itemHorizontalPosition - horizontalPosition) < abs(offsetAdjusment) { + // If the drag velocity is lower than the minimum velocity (no matter the direction): + // snap the current cell to it's original position. + if abs(velocity.x) < self.minimumSnapVelocity { + offsetAdjusment = itemHorizontalPosition - horizontalPosition + } + // If the velocity is higher than the snap threshold and drag is right->left: + // move to the next cell on the right. + else if velocity.x > 0 { + offsetAdjusment = itemHorizontalPosition - horizontalPosition + (layoutAttributes.bounds.width + self.minimumLineSpacing) + } + // If the velocity is higher than the snap threshold and drag is left->right: + // move to the next cell on the left. + else { // velocity.x < 0 + offsetAdjusment = itemHorizontalPosition - horizontalPosition - (layoutAttributes.bounds.width + self.minimumLineSpacing) + } + } + case .vertical: + let itemVerticalPosition: CGFloat + + switch snapPosition { + case .left: + itemVerticalPosition = verticalPosition + case .center: + itemVerticalPosition = verticalPosition + case .right: + itemVerticalPosition = verticalPosition + case .top: + itemVerticalPosition = layoutAttributes.frame.minY - collectionView.contentInset.top } - // If the velocity is higher than the snap threshold and drag is left->right: - // move to the next cell on the left. - else { // velocity.x < 0 - offsetAdjusment = itemHorizontalPosition - horizontalPosition - (layoutAttributes.bounds.width + self.minimumLineSpacing) + + if abs(itemVerticalPosition - verticalPosition) < abs(offsetAdjusment) { + // If the drag velocity is lower than the minimum velocity (no matter the direction): + // snap the current cell to it's original position. + if abs(velocity.y) < self.minimumSnapVelocity { + offsetAdjusment = itemVerticalPosition - verticalPosition + } + // If the velocity is higher than the snap threshold and drag is right->left: + // move to the next cell on the right. + else if velocity.y > 0 { + offsetAdjusment = itemVerticalPosition - verticalPosition + (layoutAttributes.bounds.height + self.minimumLineSpacing) + } + // If the velocity is higher than the snap threshold and drag is left->right: + // move to the next cell on the left. + else { // velocity.x < 0 + offsetAdjusment = itemVerticalPosition - verticalPosition - (layoutAttributes.bounds.height + self.minimumLineSpacing) + } } } + + } - return CGPoint(x: proposedContentOffset.x + offsetAdjusment, y: proposedContentOffset.y) + return CGPoint(x: proposedContentOffset.x + offsetAdjusment, y: proposedContentOffset.y + offsetAdjusment) } } From e2841e560d0a596c0dbd334deec5ecf3f0fbf68b Mon Sep 17 00:00:00 2001 From: Lukas Carvajal Date: Thu, 13 May 2021 01:12:58 +0200 Subject: [PATCH 2/7] Add support for center snap in vertical scrolling --- Sources/SnappingLayout/Classes/SnappingLayout.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/SnappingLayout/Classes/SnappingLayout.swift b/Sources/SnappingLayout/Classes/SnappingLayout.swift index e66a617..85a58d9 100644 --- a/Sources/SnappingLayout/Classes/SnappingLayout.swift +++ b/Sources/SnappingLayout/Classes/SnappingLayout.swift @@ -30,7 +30,6 @@ public class SnappingLayout: UICollectionViewFlowLayout { guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) } - var offsetAdjusment = CGFloat.greatestFiniteMagnitude let horizontalPosition: CGFloat let verticalPosition: CGFloat @@ -58,7 +57,7 @@ public class SnappingLayout: UICollectionViewFlowLayout { verticalPosition = proposedContentOffset.y case .center: horizontalPosition = proposedContentOffset.x - verticalPosition = proposedContentOffset.y + verticalPosition = proposedContentOffset.y + (collectionView.bounds.height * 0.5) case .top: horizontalPosition = proposedContentOffset.x verticalPosition = proposedContentOffset.y + collectionView.bounds.height - sectionInset.top @@ -108,7 +107,7 @@ public class SnappingLayout: UICollectionViewFlowLayout { case .left: itemVerticalPosition = verticalPosition case .center: - itemVerticalPosition = verticalPosition + itemVerticalPosition = layoutAttributes.center.y case .right: itemVerticalPosition = verticalPosition case .top: From ef4d878d316eef43320d25d3bfc96613977bc144 Mon Sep 17 00:00:00 2001 From: Lukas Carvajal Date: Thu, 13 May 2021 01:17:50 +0200 Subject: [PATCH 3/7] Add support for bottom snapping on vertical scroll --- .../SnappingLayout/Classes/SnappingLayout.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/SnappingLayout/Classes/SnappingLayout.swift b/Sources/SnappingLayout/Classes/SnappingLayout.swift index 85a58d9..a4a7784 100644 --- a/Sources/SnappingLayout/Classes/SnappingLayout.swift +++ b/Sources/SnappingLayout/Classes/SnappingLayout.swift @@ -14,6 +14,7 @@ public class SnappingLayout: UICollectionViewFlowLayout { case center case right case top + case bottom } // MARK: - Properties @@ -46,7 +47,7 @@ public class SnappingLayout: UICollectionViewFlowLayout { case .right: horizontalPosition = proposedContentOffset.x + collectionView.bounds.width - sectionInset.right verticalPosition = proposedContentOffset.y - case .top: + case .top, .bottom: horizontalPosition = proposedContentOffset.x verticalPosition = proposedContentOffset.y } @@ -61,6 +62,9 @@ public class SnappingLayout: UICollectionViewFlowLayout { case .top: horizontalPosition = proposedContentOffset.x verticalPosition = proposedContentOffset.y + collectionView.bounds.height - sectionInset.top + case .bottom: + horizontalPosition = proposedContentOffset.x + verticalPosition = proposedContentOffset.y + collectionView.bounds.height - sectionInset.bottom } } @@ -79,7 +83,7 @@ public class SnappingLayout: UICollectionViewFlowLayout { itemHorizontalPosition = layoutAttributes.center.x case .right: itemHorizontalPosition = layoutAttributes.frame.maxX + collectionView.contentInset.right - case .top: + case .top, .bottom: itemHorizontalPosition = horizontalPosition } @@ -104,14 +108,14 @@ public class SnappingLayout: UICollectionViewFlowLayout { let itemVerticalPosition: CGFloat switch snapPosition { - case .left: + case .left, .right: itemVerticalPosition = verticalPosition case .center: itemVerticalPosition = layoutAttributes.center.y - case .right: - itemVerticalPosition = verticalPosition case .top: itemVerticalPosition = layoutAttributes.frame.minY - collectionView.contentInset.top + case .bottom: + itemVerticalPosition = layoutAttributes.frame.minY + collectionView.contentInset.bottom } if abs(itemVerticalPosition - verticalPosition) < abs(offsetAdjusment) { From 20f0eb9035ac75c4eeb4f188fdadd679f3fde8ce Mon Sep 17 00:00:00 2001 From: Lukas Carvajal Date: Thu, 13 May 2021 01:43:46 +0200 Subject: [PATCH 4/7] Add section on vertical scrolling to README --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index dc435d7..c9bad09 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,17 @@ snappingLayout.snapPosition = .right ![right](readmeImages/right.gif) +### Vertical scrolling + +With vertical scrolling enabled, `.top`, `.center`, and `.bottom` can snap the cell to their respective parts of the collection view. + +```swift +let snappingLayout = SnappingLayout() +snappingLayout.snapPosition = .top +``` + +![top](readmeImages/top.gif) + ## 💬 Contributing This is an open source project, so feel free to contribute. How? From 93ade53b239c7b69bdc1a8940e2ca0c4b2d75390 Mon Sep 17 00:00:00 2001 From: Lukas Carvajal Date: Thu, 13 May 2021 01:58:25 +0200 Subject: [PATCH 5/7] Update comments describing scrolling logic --- Sources/SnappingLayout/Classes/SnappingLayout.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SnappingLayout/Classes/SnappingLayout.swift b/Sources/SnappingLayout/Classes/SnappingLayout.swift index a4a7784..0d89461 100644 --- a/Sources/SnappingLayout/Classes/SnappingLayout.swift +++ b/Sources/SnappingLayout/Classes/SnappingLayout.swift @@ -124,13 +124,13 @@ public class SnappingLayout: UICollectionViewFlowLayout { if abs(velocity.y) < self.minimumSnapVelocity { offsetAdjusment = itemVerticalPosition - verticalPosition } - // If the velocity is higher than the snap threshold and drag is right->left: - // move to the next cell on the right. + // If the velocity is higher than the snap threshold and drag is bottom->top: + // move to the next cell on the bottom. else if velocity.y > 0 { offsetAdjusment = itemVerticalPosition - verticalPosition + (layoutAttributes.bounds.height + self.minimumLineSpacing) } - // If the velocity is higher than the snap threshold and drag is left->right: - // move to the next cell on the left. + // If the velocity is higher than the snap threshold and drag is top->bottom: + // move to the next cell on the top. else { // velocity.x < 0 offsetAdjusment = itemVerticalPosition - verticalPosition - (layoutAttributes.bounds.height + self.minimumLineSpacing) } From ba542b35ac39ad2c89d9258b3d40b0870a18ce9c Mon Sep 17 00:00:00 2001 From: Lukas Carvajal Date: Thu, 13 May 2021 02:23:12 +0200 Subject: [PATCH 6/7] Add switch statement to return statement --- Sources/SnappingLayout/Classes/SnappingLayout.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/SnappingLayout/Classes/SnappingLayout.swift b/Sources/SnappingLayout/Classes/SnappingLayout.swift index 0d89461..e461d4d 100644 --- a/Sources/SnappingLayout/Classes/SnappingLayout.swift +++ b/Sources/SnappingLayout/Classes/SnappingLayout.swift @@ -140,6 +140,11 @@ public class SnappingLayout: UICollectionViewFlowLayout { } - return CGPoint(x: proposedContentOffset.x + offsetAdjusment, y: proposedContentOffset.y + offsetAdjusment) + switch scrollDirection { + case .horizontal: + return CGPoint(x: proposedContentOffset.x + offsetAdjusment, y: proposedContentOffset.y) + case .vertical: + return CGPoint(x: proposedContentOffset.x, y: proposedContentOffset.y + offsetAdjusment) + } } } From 16b9cd34ac1292531674877d4f12278802ca5650 Mon Sep 17 00:00:00 2001 From: Lukas Carvajal Date: Thu, 13 May 2021 02:26:51 +0200 Subject: [PATCH 7/7] Remove horizontal from comment on scroll velocity --- Sources/SnappingLayout/Classes/SnappingLayout.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SnappingLayout/Classes/SnappingLayout.swift b/Sources/SnappingLayout/Classes/SnappingLayout.swift index e461d4d..55eef3f 100644 --- a/Sources/SnappingLayout/Classes/SnappingLayout.swift +++ b/Sources/SnappingLayout/Classes/SnappingLayout.swift @@ -22,7 +22,7 @@ public class SnappingLayout: UICollectionViewFlowLayout { /// Position to snap the cells. public var snapPosition = SnapPositionType.center - /// Minimum horizontal velocity to trigger the snap effect. + /// Minimum velocity to trigger the snap effect. private let minimumSnapVelocity: CGFloat = 0.3 // MARK: - UICollectionViewFlowLayout