Skip to content

Instantly share code, notes, and snippets.

@kientux
Forked from vinhnx/uicollectionview+centerPaging.md
Last active November 25, 2021 10:13
Show Gist options
  • Save kientux/4a85914d3041b2afd2ca5d80d0af6f21 to your computer and use it in GitHub Desktop.
Save kientux/4a85914d3041b2afd2ca5d80d0af6f21 to your computer and use it in GitHub Desktop.
Centered Paging with Preview Cells on UICollectionView

Centered Paging with Preview Cells on UICollectionView

The proposed offset is where the collection view would stop without our intervention. We peek into this area by finding its centre as proposedContentOffsetCenterX and examine our currently visible cells to see which one’s centre is closer to the centre of that area.

class CenterItemPagingCollectionViewLayout: UICollectionViewFlowLayout {
    
    private var mostRecentOffset: CGPoint = .zero {
        didSet {
            notifyPageChanged()
        }
    }
    
    var pagingItemDidChanged: ((Int) -> Void)?
    
    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint,
                                      withScrollingVelocity velocity: CGPoint) -> CGPoint {
        
        if velocity.x == 0 {
            return mostRecentOffset
        }
        
        guard let cv = self.collectionView else {
            mostRecentOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset,
                                                         withScrollingVelocity: velocity)
            return mostRecentOffset
        }
            
        let cvBounds = cv.bounds
        let halfWidth = cvBounds.size.width * 0.5
        
        guard let attributesForVisibleCells = self.layoutAttributesForElements(in: cvBounds) else {
            mostRecentOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset,
                                                         withScrollingVelocity: velocity)
            return mostRecentOffset
        }
        
        var candidateAttributes: UICollectionViewLayoutAttributes?
        for attributes in attributesForVisibleCells {
            
            // == Skip comparison with non-cell items (headers and footers) == //
            if attributes.representedElementCategory != .cell {
                continue
            }
            
            if (attributes.center.x == 0) || (attributes.center.x > (cv.contentOffset.x + halfWidth) && velocity.x < 0) {
                continue
            }
            
            candidateAttributes = attributes
        }
        
        // Beautification step , I don't know why it works!
        if proposedContentOffset.x == -(cv.contentInset.left) {
            mostRecentOffset = proposedContentOffset
            return mostRecentOffset
        }
        
        guard let attrs = candidateAttributes else {
            return mostRecentOffset
        }
        
        mostRecentOffset = CGPoint(x: floor(attrs.center.x - halfWidth), y: proposedContentOffset.y)
        return mostRecentOffset
    }
    
    private func notifyPageChanged() {
        guard let collectionView = collectionView else {
            return
        }
        
        // take y as middle of collectionView, as mostRecentOffset might not be at any cell
        let centerYOffset = CGPoint(x: mostRecentOffset.x,
                                    y: collectionView.bounds.height / 2.0)
        guard let indexPath = collectionView.indexPathForItem(at: centerYOffset) else {
            // if offset.x <= 0, then it the first page because the first item has no previous item, hence indexPath returns nil
            pagingItemDidChanged?(0)
            return
        }
        
        // previous cell is always visible so the centered cell is the next indexPath
        pagingItemDidChanged?(indexPath.item + 1)
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment