Paging UICollectionView by cells, not screen

前端 未结 22 1943
予麋鹿
予麋鹿 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:40

    Swift 5

    I've found a way to do this without subclassing UICollectionView, just calculating the contentOffset in horizontal. Obviously without isPagingEnabled set true. Here is the code:

    var offsetScroll1 : CGFloat = 0
    var offsetScroll2 : CGFloat = 0
    let flowLayout = UICollectionViewFlowLayout()
    let screenSize : CGSize = UIScreen.main.bounds.size
    var items = ["1", "2", "3", "4", "5"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        flowLayout.scrollDirection = .horizontal
        flowLayout.minimumLineSpacing = 7
        let collectionView = UICollectionView(frame: CGRect(x: 0, y: 590, width: screenSize.width, height: 200), collectionViewLayout: flowLayout)
        collectionView.register(collectionViewCell1.self, forCellWithReuseIdentifier: cellReuseIdentifier)
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.backgroundColor = UIColor.clear
        collectionView.showsHorizontalScrollIndicator = false
        self.view.addSubview(collectionView)
    }
    
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        offsetScroll1 = offsetScroll2
    }
    
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        offsetScroll1 = offsetScroll2
    }
    
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>){
        let indexOfMajorCell = self.desiredIndex()
        let indexPath = IndexPath(row: indexOfMajorCell, section: 0)
        flowLayout.collectionView!.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        targetContentOffset.pointee = scrollView.contentOffset
    }
    
    private func desiredIndex() -> Int {
        var integerIndex = 0
        print(flowLayout.collectionView!.contentOffset.x)
        offsetScroll2 = flowLayout.collectionView!.contentOffset.x
        if offsetScroll2 > offsetScroll1 {
            integerIndex += 1
            let offset = flowLayout.collectionView!.contentOffset.x / screenSize.width
            integerIndex = Int(round(offset))
            if integerIndex < (items.count - 1) {
                integerIndex += 1
            }
        }
        if offsetScroll2 < offsetScroll1 {
            let offset = flowLayout.collectionView!.contentOffset.x / screenSize.width
            integerIndex = Int(offset.rounded(.towardZero))
        }
        let targetIndex = integerIndex
        return targetIndex
    }
    
    0 讨论(0)
  • 2020-12-04 05:40

    i created a custom collection view layout here that supports:

    • paging one cell at a time
    • paging 2+ cells at a time depending on swipe velocity
    • horizontal or vertical directions

    it's as easy as:

    let layout = PagingCollectionViewLayout()
    
    layout.itemSize = 
    layout.minimumLineSpacing = 
    layout.scrollDirection = 
    

    you can just add PagingCollectionViewLayout.swift to your project

    or

    add pod 'PagingCollectionViewLayout' to your podfile

    0 讨论(0)
  • 2020-12-04 05:43

    This is my solution, in Swift 4.2, I wish it could help you.

    class SomeViewController: UIViewController {
    
      private lazy var flowLayout: UICollectionViewFlowLayout = {
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width: /* width */, height: /* height */)
        layout.minimumLineSpacing = // margin
        layout.minimumInteritemSpacing = 0.0
        layout.sectionInset = UIEdgeInsets(top: 0.0, left: /* margin */, bottom: 0.0, right: /* margin */)
        layout.scrollDirection = .horizontal
        return layout
      }()
    
      private lazy var collectionView: UICollectionView = {
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
        collectionView.showsHorizontalScrollIndicator = false
        collectionView.dataSource = self
        collectionView.delegate = self
        // collectionView.register(SomeCell.self)
        return collectionView
      }()
    
      private var currentIndex: Int = 0
    }
    
    // MARK: - UIScrollViewDelegate
    
    extension SomeViewController {
      func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        guard scrollView == collectionView else { return }
    
        let pageWidth = flowLayout.itemSize.width + flowLayout.minimumLineSpacing
        currentIndex = Int(scrollView.contentOffset.x / pageWidth)
      }
    
      func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        guard scrollView == collectionView else { return }
    
        let pageWidth = flowLayout.itemSize.width + flowLayout.minimumLineSpacing
        var targetIndex = Int(roundf(Float(targetContentOffset.pointee.x / pageWidth)))
        if targetIndex > currentIndex {
          targetIndex = currentIndex + 1
        } else if targetIndex < currentIndex {
          targetIndex = currentIndex - 1
        }
        let count = collectionView.numberOfItems(inSection: 0)
        targetIndex = max(min(targetIndex, count - 1), 0)
        print("targetIndex: \(targetIndex)")
    
        targetContentOffset.pointee = scrollView.contentOffset
        var offsetX: CGFloat = 0.0
        if targetIndex < count - 1 {
          offsetX = pageWidth * CGFloat(targetIndex)
        } else {
          offsetX = scrollView.contentSize.width - scrollView.width
        }
        collectionView.setContentOffset(CGPoint(x: offsetX, y: 0.0), animated: true)
      }
    }
    
    0 讨论(0)
  • 2020-12-04 05:43

    You can use the following library: https://github.com/ink-spot/UPCarouselFlowLayout

    It's very simple and ofc you do not need to think about details like other answers contain.

    0 讨论(0)
  • 2020-12-04 05:45

    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:

    1. Add the following property to your type: private var indexOfCellBeforeDragging = 0
    2. Set the collectionView delegate like this: collectionView.delegate = self
    3. Add conformance to UICollectionViewDelegate via an extension: extension YourType: UICollectionViewDelegate { }
    4. Add the following method to the extension implementing the UICollectionViewDelegate conformance and set a value for pageWidth:

      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))
      }
      
    5. Add the following method to the extension implementing the UICollectionViewDelegate conformance, set the same value for pageWidth (you may also store this value at a central place) and set a value for collectionViewItemCount:

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

    Kind of like evya's answer, but a little smoother because it doesn't set the targetContentOffset to zero.

    - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
        if ([scrollView isKindOfClass:[UICollectionView class]]) {
            UICollectionView* collectionView = (UICollectionView*)scrollView;
            if ([collectionView.collectionViewLayout isKindOfClass:[UICollectionViewFlowLayout class]]) {
                UICollectionViewFlowLayout* layout = (UICollectionViewFlowLayout*)collectionView.collectionViewLayout;
    
                CGFloat pageWidth = layout.itemSize.width + layout.minimumInteritemSpacing;
                CGFloat usualSideOverhang = (scrollView.bounds.size.width - pageWidth)/2.0;
                // k*pageWidth - usualSideOverhang = contentOffset for page at index k if k >= 1, 0 if k = 0
                // -> (contentOffset + usualSideOverhang)/pageWidth = k at page stops
    
                NSInteger targetPage = 0;
                CGFloat currentOffsetInPages = (scrollView.contentOffset.x + usualSideOverhang)/pageWidth;
                targetPage = velocity.x < 0 ? floor(currentOffsetInPages) : ceil(currentOffsetInPages);
                targetPage = MAX(0,MIN(self.projects.count - 1,targetPage));
    
                *targetContentOffset = CGPointMake(MAX(targetPage*pageWidth - usualSideOverhang,0), 0);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题