Paging UICollectionView by cells, not screen
OK, so I found the solution here: targetContentOffsetForProposedContentOffset:withScrollingVelocity without subclassing UICollectionViewFlowLayout
I should have searched for targetContentOffsetForProposedContentOffset
in the begining.
just override the method:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { *targetContentOffset = scrollView.contentOffset; // set acceleration to 0.0 float pageWidth = (float)self.articlesCollectionView.bounds.size.width; int minSpace = 10; int cellToSwipe = (scrollView.contentOffset.x)/(pageWidth + minSpace) + 0.5; // cell width + min spacing for lines if (cellToSwipe < 0) { cellToSwipe = 0; } else if (cellToSwipe >= self.articles.count) { cellToSwipe = self.articles.count - 1; } [self.articlesCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:cellToSwipe inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:YES];}
Horizontal Paging With Custom Page Width (Swift 4 & 5)
Many solutions presented here result in some weird behaviour that doesn't feel like properly implemented paging.
The solution presented in this tutorial, however, doesn't seem to have any issues. It just feels like a perfectly working paging algorithm. You can implement it in 5 simple steps:
- Add the following property to your type:
private var indexOfCellBeforeDragging = 0
- Set the
collectionView
delegate
like this:collectionView.delegate = self
- Add conformance to
UICollectionViewDelegate
via an extension:extension YourType: UICollectionViewDelegate { }
Add the following method to the extension implementing the
UICollectionViewDelegate
conformance and set a value forpageWidth
:func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { let pageWidth = // The width your page should have (plus a possible margin) let proportionalOffset = collectionView.contentOffset.x / pageWidth indexOfCellBeforeDragging = Int(round(proportionalOffset))}
Add the following method to the extension implementing the
UICollectionViewDelegate
conformance, set the same value forpageWidth
(you may also store this value at a central place) and set a value forcollectionViewItemCount
:func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { // Stop scrolling targetContentOffset.pointee = scrollView.contentOffset // Calculate conditions let pageWidth = // The width your page should have (plus a possible margin) let collectionViewItemCount = // The number of items in this section let proportionalOffset = collectionView.contentOffset.x / pageWidth let indexOfMajorCell = Int(round(proportionalOffset)) let swipeVelocityThreshold: CGFloat = 0.5 let hasEnoughVelocityToSlideToTheNextCell = indexOfCellBeforeDragging + 1 < collectionViewItemCount && velocity.x > swipeVelocityThreshold let hasEnoughVelocityToSlideToThePreviousCell = indexOfCellBeforeDragging - 1 >= 0 && velocity.x < -swipeVelocityThreshold let majorCellIsTheCellBeforeDragging = indexOfMajorCell == indexOfCellBeforeDragging let didUseSwipeToSkipCell = majorCellIsTheCellBeforeDragging && (hasEnoughVelocityToSlideToTheNextCell || hasEnoughVelocityToSlideToThePreviousCell) if didUseSwipeToSkipCell { // Animate so that swipe is just continued let snapToIndex = indexOfCellBeforeDragging + (hasEnoughVelocityToSlideToTheNextCell ? 1 : -1) let toValue = pageWidth * CGFloat(snapToIndex) UIView.animate( withDuration: 0.3, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: velocity.x, options: .allowUserInteraction, animations: { scrollView.contentOffset = CGPoint(x: toValue, y: 0) scrollView.layoutIfNeeded() }, completion: nil ) } else { // Pop back (against velocity) let indexPath = IndexPath(row: indexOfMajorCell, section: 0) collectionView.scrollToItem(at: indexPath, at: .left, animated: true) }}