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? diff --git a/Sources/SnappingLayout/Classes/SnappingLayout.swift b/Sources/SnappingLayout/Classes/SnappingLayout.swift index 9521346..55eef3f 100644 --- a/Sources/SnappingLayout/Classes/SnappingLayout.swift +++ b/Sources/SnappingLayout/Classes/SnappingLayout.swift @@ -13,6 +13,8 @@ public class SnappingLayout: UICollectionViewFlowLayout { case left case center case right + case top + case bottom } // MARK: - Properties @@ -20,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 @@ -29,52 +31,120 @@ 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, .bottom: + 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 + (collectionView.bounds.height * 0.5) + 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 + } + } + + 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, .bottom: + itemHorizontalPosition = horizontalPosition + } + + 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) + } } - // 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) + case .vertical: + let itemVerticalPosition: CGFloat + + switch snapPosition { + case .left, .right: + itemVerticalPosition = verticalPosition + case .center: + itemVerticalPosition = layoutAttributes.center.y + case .top: + itemVerticalPosition = layoutAttributes.frame.minY - collectionView.contentInset.top + case .bottom: + itemVerticalPosition = layoutAttributes.frame.minY + collectionView.contentInset.bottom } - // 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 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 top->bottom: + // move to the next cell on the top. + else { // velocity.x < 0 + offsetAdjusment = itemVerticalPosition - verticalPosition - (layoutAttributes.bounds.height + self.minimumLineSpacing) + } } } + + } - return CGPoint(x: proposedContentOffset.x + offsetAdjusment, y: proposedContentOffset.y) + switch scrollDirection { + case .horizontal: + return CGPoint(x: proposedContentOffset.x + offsetAdjusment, y: proposedContentOffset.y) + case .vertical: + return CGPoint(x: proposedContentOffset.x, y: proposedContentOffset.y + offsetAdjusment) + } } }