Paging UICollectionView by cells, not screen

前端 未结 22 1942
予麋鹿
予麋鹿 2020-12-04 05:00

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

相关标签:
22条回答
  • 2020-12-04 05:53

    Here's my implementation in Swift 5 for vertical cell-based paging:

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
    
        guard let collectionView = self.collectionView else {
            let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
            return latestOffset
        }
    
        // 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 = collectionView.contentOffset.y/pageHeight
    
        // Determine the current page based on velocity.
        let currentPage = velocity.y == 0 ? round(approximatePage) : (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) - collectionView.contentInset.top
    
        return CGPoint(x: proposedContentOffset.x, y: newVerticalOffset)
    }
    

    Some notes:

    • Doesn't glitch
    • SET PAGING TO FALSE! (otherwise this won't work)
    • Allows you to set your own flickvelocity easily.
    • If something is still not working after trying this, check if your itemSize actually matches the size of the item as that's often a problem, especially when using collectionView(_:layout:sizeForItemAt:), use a custom variable with the itemSize instead.
    • This works best when you set self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast.

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

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
    
        guard let collectionView = self.collectionView else {
            let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
            return latestOffset
        }
    
        // 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 = collectionView.contentOffset.x/pageWidth
    
        // Determine the current page based on velocity.
        let currentPage = velocity.x == 0 ? round(approximatePage) : (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 newHorizontalOffset.
        let newHorizontalOffset = ((currentPage + flickedPages) * pageWidth) - collectionView.contentInset.left
    
        return CGPoint(x: newHorizontalOffset, y: proposedContentOffset.y)
    }
    

    This code is based on the code I use in my personal project, you can check it out here by downloading it and running the Example target.

    0 讨论(0)
  • 2020-12-04 05:54
    final class PagingFlowLayout: UICollectionViewFlowLayout {
        private var currentIndex = 0
    
        override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
            let count = collectionView!.numberOfItems(inSection: 0)
            let currentAttribute = layoutAttributesForItem(
                at: IndexPath(item: currentIndex, section: 0)
                ) ?? UICollectionViewLayoutAttributes()
    
            let direction = proposedContentOffset.x > currentAttribute.frame.minX
            if collectionView!.contentOffset.x + collectionView!.bounds.width < collectionView!.contentSize.width || currentIndex < count - 1 {
                currentIndex += direction ? 1 : -1
                currentIndex = max(min(currentIndex, count - 1), 0)
            }
    
            let indexPath = IndexPath(item: currentIndex, section: 0)
            let closestAttribute = layoutAttributesForItem(at: indexPath) ?? UICollectionViewLayoutAttributes()
    
            let centerOffset = collectionView!.bounds.size.width / 2
            return CGPoint(x: closestAttribute.center.x - centerOffset, y: 0)
        }
    }
    
    0 讨论(0)
  • 2020-12-04 05:58

    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];
    }
    
    0 讨论(0)
  • 2020-12-04 05:58

    Ok so the proposed answers did'nt worked for me because I wanted to scroll by sections instead, and thus, have variable width page sizes

    I did this (vertical only):

       var pagesSizes = [CGSize]()
       func scrollViewDidScroll(_ scrollView: UIScrollView) {
            defer {
                lastOffsetY = scrollView.contentOffset.y
            }
            if collectionView.isDecelerating {
                var currentPage = 0
                var currentPageBottom = CGFloat(0)
                for pagesSize in pagesSizes {
                    currentPageBottom += pagesSize.height
                    if currentPageBottom > collectionView!.contentOffset.y {
                        break
                    }
                    currentPage += 1
                }
                if collectionView.contentOffset.y > currentPageBottom - pagesSizes[currentPage].height, collectionView.contentOffset.y + collectionView.frame.height < currentPageBottom {
                    return // 100% of view within bounds
                }
                if lastOffsetY < collectionView.contentOffset.y {
                    if currentPage + 1 != pagesSizes.count {
                        collectionView.setContentOffset(CGPoint(x: 0, y: currentPageBottom), animated: true)
                    }
                } else {
                    collectionView.setContentOffset(CGPoint(x: 0, y: currentPageBottom - pagesSizes[currentPage].height), animated: true)
                }
            }
        }
    

    In this case, I calculate each page size beforehand using the section height + header + footer, and store it in the array. That's the pagesSizes member

    0 讨论(0)
提交回复
热议问题