Paging UICollectionView by cells, not screen

2019-03-07 11:11发布

I have UICollectionView with horizontal scrolling and there are always 2 cells side-by-side per the entire screen. I need the scrolling to stop at the begining of a cell. With paging enabled, the collection view scrolls the whole page, which is 2 cells at once, and then it stops.

I need to enable scrolling by a single cell, or scrolling by multiple cells with stopping at the edge of the cell.

I tried to subclass UICollectionViewFlowLayout and to implement the method targetContentOffsetForProposedContentOffset, but so far I was only able to break my collection view and it stopped scrolling. Is there any easier way to achieve this and how, or do I really need to implement all methods of UICollectionViewFlowLayout subclass? Thanks.

14条回答
狗以群分
2楼-- · 2019-03-07 11:42

Partly based on StevenOjo's answer. I've tested this using a horizontal scrolling and no Bounce UICollectionView. cellSize is CollectionViewCell size. You can tweak factor to modify scrolling sensitivity.

override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = scrollView.contentOffset
    var factor: CGFloat = 0.5
    if velocity.x < 0 {
        factor = -factor
    }
    let indexPath = IndexPath(row: (scrollView.contentOffset.x/cellSize.width + factor).int, section: 0)
    collectionView?.scrollToItem(at: indexPath, at: .left, animated: true)
}
查看更多
3楼-- · 2019-03-07 11:48

OK, so I found the solution here: targetContentOffsetForProposedContentOffset:withScrollingVelocity without subclassing UICollectionViewFlowLayout

I should have searched for targetContentOffsetForProposedContentOffset in the begining.

查看更多
小情绪 Triste *
4楼-- · 2019-03-07 11:48

Here is my version of it in Swift 3. Calculate the offset after scrolling ended and adjust the offset with animation.

collectionLayout is a UICollectionViewFlowLayout()

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    let index = scrollView.contentOffset.x / collectionLayout.itemSize.width
    let fracPart = index.truncatingRemainder(dividingBy: 1)
    let item= Int(fracPart >= 0.5 ? ceil(index) : floor(index))

    let indexPath = IndexPath(item: item, section: 0)
    collectionView.scrollToItem(at: indexPath, at: .left, animated: true)
}
查看更多
闹够了就滚
5楼-- · 2019-03-07 11:49

Approach 1: Collection View

flowLayout is UICollectionViewFlowLayout property

override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    if let collectionView = collectionView {

        targetContentOffset.memory = scrollView.contentOffset
        let pageWidth = CGRectGetWidth(scrollView.frame) + flowLayout.minimumInteritemSpacing

        var assistanceOffset : CGFloat = pageWidth / 3.0

        if velocity.x < 0 {
            assistanceOffset = -assistanceOffset
        }

        let assistedScrollPosition = (scrollView.contentOffset.x + assistanceOffset) / pageWidth

        var targetIndex = Int(round(assistedScrollPosition))


        if targetIndex < 0 {
            targetIndex = 0
        }
        else if targetIndex >= collectionView.numberOfItemsInSection(0) {
            targetIndex = collectionView.numberOfItemsInSection(0) - 1
        }

        print("targetIndex = \(targetIndex)")

        let indexPath = NSIndexPath(forItem: targetIndex, inSection: 0)

        collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Left, animated: true)
    }
}

Approach 2: Page View Controller

You could use UIPageViewController if it meets your requirements, each page would have a separate view controller.

查看更多
时光不老,我们不散
6楼-- · 2019-03-07 11:49

Here's the easiest way that i found to do that in Swift 4.2 for horinzontal scroll:

I'm using the first cell on visibleCells and scrolling to then, if the first visible cell are showing less of the half of it's width i'm scrolling to the next one.

If your collection scroll vertically, simply change x by y and width by height

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = scrollView.contentOffset
    var indexes = self.collectionView.indexPathsForVisibleItems
    indexes.sort()
    var index = indexes.first!
    let cell = self.collectionView.cellForItem(at: index)!
    let position = self.collectionView.contentOffset.x - cell.frame.origin.x
    if position > cell.frame.size.width/2{
       index.row = index.row+1
    }
    self.collectionView.scrollToItem(at: index, at: .left, animated: true )
}
查看更多
叛逆
7楼-- · 2019-03-07 11:49

Yes, here's my implementation in Swift 4.1 for vertical cell-based paging:

override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

    // Page height used for estimating and calculating paging.
    let pageHeight = self.itemSize.height + self.minimumLineSpacing

    // Make an estimation of the current page position.
    let approximatePage = self.collectionView!.contentOffset.y/pageHeight

    // Determine the current page based on velocity.
    let currentPage = (velocity.y < 0.0) ? floor(approximatePage) : ceil(approximatePage)

    // Create custom flickVelocity.
    let flickVelocity = velocity.y * 0.3

    // Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
    let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)

    let newVerticalOffset = ((currentPage + flickedPages) * pageHeight) - self.collectionView!.contentInset.top

    return CGPoint(x: proposedContentOffset.x, y: newVerticalOffset)
}

This shouldn't glitch and allows you to set your own flickvelocity easily.

edit: Here's a horizontal version (haven't tested it thoroughly so please forgive any mistakes):

internal override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

    // Page width used for estimating and calculating paging.
    let pageWidth = self.itemSize.width + self.minimumInteritemSpacing

    // Make an estimation of the current page position.
    let approximatePage = self.collectionView!.contentOffset.x/pageWidth

    // Determine the current page based on velocity.
    let currentPage = (velocity.x < 0.0) ? floor(approximatePage) : ceil(approximatePage)

    // Create custom flickVelocity.
    let flickVelocity = velocity.x * 0.3

    // Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
    let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)

    // Calculate newVerticalOffset.
    let newHorizontalOffset = ((currentPage + flickedPages) * pageWidth) - self.collectionView!.contentInset.left

    return CGPoint(x: newHorizontalOffset, y: proposedContentOffset.y)
}
查看更多
登录 后发表回答