Last active
June 28, 2022 15:40
Horizontal paging for collections based on the content size
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentSizePagingCollectionDelegate.swift | |
// CardsCarousel | |
// | |
// Created by Daniel Carlos Souza Carvalho on 2/20/21. | |
// | |
import UIKit | |
/// Horizontal paging for collections based on the content size | |
/// | |
/// While isPagingEnabled stops on multiples of the scroll view’s **bounds** when the user scrolls, | |
/// this calculates the offset based on the resulting **content size**, given the cell width and the cell distances. | |
/// | |
/// Make sure to set isPagingEnabled to false in the collection | |
/// | |
/// After reloading the data, call collection.layoutIfNeeded(). | |
/// That way collection.contentSize = collection.collectionViewLayout.collectionViewContentSize | |
class ContentSizePagingCollectionDelegate: NSObject, UICollectionViewDelegateFlowLayout { | |
private let cellWitdhPercentage: CGFloat = 0.85 | |
private let idealCellDistance: CGFloat = 16 | |
private var currentPositionIndex = 0 | |
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { | |
let cellWidth = cellWitdh(forCollectionViewWidth: collectionView.frame.width) | |
return CGSize(width: cellWidth, height: collectionView.frame.height) | |
} | |
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { | |
return cellDistance(forCollectionViewWidth: collectionView.frame.width) | |
} | |
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { | |
let collectionViewWidth = collectionView.frame.width | |
let sides = collectionViewWidth - cellWitdh(forCollectionViewWidth: collectionViewWidth) | |
let horizontalInset: CGFloat = sides/2 | |
return UIEdgeInsets(top: 0, left: horizontalInset, bottom: 0, right: horizontalInset) | |
} | |
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { | |
let offsetWidthForOneItem = caculateContentOffsetForOneItem(scrollView) | |
let offsetForCurrentItem = { CGPoint(x: offsetWidthForOneItem * CGFloat(self.currentPositionIndex), y: targetContentOffset.pointee.y) } | |
enum HorizontalDirection { case left, right } | |
let horizontalDirection: HorizontalDirection = velocity.x > 0 ? .right : .left | |
switch horizontalDirection { | |
case .left: | |
let isFirstItem = currentPositionIndex <= 0 | |
guard isFirstItem == false else { | |
targetContentOffset.pointee = offsetForCurrentItem() | |
return | |
} | |
currentPositionIndex -= 1 | |
targetContentOffset.pointee = offsetForCurrentItem() | |
case .right: | |
let isLastItem = (scrollView.contentOffset.x + offsetWidthForOneItem >= scrollView.contentSize.width) | |
guard isLastItem == false else { | |
targetContentOffset.pointee = offsetForCurrentItem() | |
return | |
} | |
currentPositionIndex += 1 | |
targetContentOffset.pointee = offsetForCurrentItem() | |
} | |
} | |
private func cellWitdh(forCollectionViewWidth collectionViewWidth: CGFloat) -> CGFloat { | |
let itemWidth = collectionViewWidth * cellWitdhPercentage | |
return itemWidth | |
} | |
private func cellDistance(forCollectionViewWidth collectionViewWidth: CGFloat) -> CGFloat { | |
let sides = collectionViewWidth - cellWitdh(forCollectionViewWidth: collectionViewWidth) | |
let oneSide = sides/2 | |
let final = min(idealCellDistance, oneSide/2) | |
return final | |
} | |
private func caculateContentOffsetForOneItem(_ scrollView: UIScrollView) -> CGFloat { | |
let cellItemWidth = cellWitdh(forCollectionViewWidth: scrollView.frame.width) | |
let sides = scrollView.frame.width - cellItemWidth | |
let oneSide: CGFloat = sides/2 | |
let nextItemVisiblePart = scrollView.frame.width - (oneSide + cellItemWidth + cellDistance(forCollectionViewWidth: scrollView.frame.width)) | |
return oneSide + (cellItemWidth - nextItemVisiblePart) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment