【问题标题】:Snap to center of a cell when scrolling UICollectionView horizontally水平滚动 UICollectionView 时对齐单元格的中心
【发布时间】:2016-02-24 16:02:07
【问题描述】:

我知道以前有人问过这个问题,但他们都是关于 UITableViewsUIScrollViews 的,而我无法获得适用于我的公认解决方案。我想要的是水平滚动我的UICollectionView 时的捕捉效果——就像在 iOS AppStore 中发生的一样。 iOS 9+ 是我的目标版本,所以在回答这个问题之前请先查看 UIKit 的变化。

谢谢。

【问题讨论】:

    标签: ios objective-c uiscrollview uicollectionview


    【解决方案1】:

    虽然我最初使用的是 Objective-C,但后来我改用了 Swift,原来接受的答案还不够。

    我最终创建了一个 UICollectionViewLayout 子类,它提供了最好的 (imo) 体验,而不是在用户停止滚动时改变内容偏移或类似内容的其他功能。

    class SnappingCollectionViewLayout: UICollectionViewFlowLayout {
    
        override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
            guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) }
    
            var offsetAdjustment = CGFloat.greatestFiniteMagnitude
            let horizontalOffset = proposedContentOffset.x + collectionView.contentInset.left
    
            let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
    
            let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)
    
            layoutAttributesArray?.forEach({ (layoutAttributes) in
                let itemOffset = layoutAttributes.frame.origin.x
                if fabsf(Float(itemOffset - horizontalOffset)) < fabsf(Float(offsetAdjustment)) {
                    offsetAdjustment = itemOffset - horizontalOffset
                }
            })
    
            return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
        }
    }
    

    对于当前布局子类最原生的感觉减速,请确保设置以下内容:

    collectionView?.decelerationRate = UIScrollViewDecelerationRateFast

    【讨论】:

    • 由于某种原因,这导致我的收藏视图是垂直的,并调整了我所有单元格的大小,有什么见解吗?
    • 最后一行是一个很好的提示!没听说过这个属性!
    • 对于设置了sectionInset.left的人,将return语句替换为:return CGPoint(x: proposedContentOffset.x + offsetAdjustment - sectionInset.left, y: proposedContentOffset.y)
    • 这个解决方案虽然可以捕捉单元格,但它的动画非常有问题,因为它不尊重速度。如果用户滑动非常快,您会看到它出现很多故障。
    • 我需要它在滑动时始终捕捉到下一个单元格,所以我通过检查滑动方向对其进行了一些调整:layoutAttributesArray?.forEach({ (layoutAttributes) in let itemOffset = layoutAttributes.frame.origin.x let itemWidth = Float(layoutAttributes.frame.width) let direction: Float = velocity.x &gt; 0 ? 1 : -1 if fabsf(Float(itemOffset - horizontalOffset)) &lt; fabsf(Float(offsetAdjustment)) + itemWidth * direction { offsetAdjustment = itemOffset - horizontalOffset } })
    【解决方案2】:

    根据 Mete 的回答和 Chris Chute 的评论,

    这是一个 Swift 4 扩展,它可以满足 OP 的需求。它在单行和双行嵌套集合视图上进行了测试,效果很好。

    extension UICollectionView {
        func scrollToNearestVisibleCollectionViewCell() {
            self.decelerationRate = UIScrollViewDecelerationRateFast
            let visibleCenterPositionOfScrollView = Float(self.contentOffset.x + (self.bounds.size.width / 2))
            var closestCellIndex = -1
            var closestDistance: Float = .greatestFiniteMagnitude
            for i in 0..<self.visibleCells.count {
                let cell = self.visibleCells[i]
                let cellWidth = cell.bounds.size.width
                let cellCenter = Float(cell.frame.origin.x + cellWidth / 2)
    
                // Now calculate closest cell
                let distance: Float = fabsf(visibleCenterPositionOfScrollView - cellCenter)
                if distance < closestDistance {
                    closestDistance = distance
                    closestCellIndex = self.indexPath(for: cell)!.row
                }
            }
            if closestCellIndex != -1 {
                self.scrollToItem(at: IndexPath(row: closestCellIndex, section: 0), at: .centeredHorizontally, animated: true)
            }
        }
    }
    

    你需要为你的collection view实现UIScrollViewDelegate协议,然后添加这两个方法:

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        self.collectionView.scrollToNearestVisibleCollectionViewCell()
    }
    
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            self.collectionView.scrollToNearestVisibleCollectionViewCell()
        }
    }
    

    【讨论】:

    • 这个答案最适合我。很好很顺利。我确实进行了更改,因为我有单元格间距并且不希望它居中。还想控制动画持续时间,所以:if closestCellIndex != -1 { UIView.animate(withDuration: 0.1) { let toX = (cellWidth + cellHorizontalSpacing) * CGFloat(closestCellIndex) scrollView.contentOffset = CGPoint(x: toX, y: 0) scrollView.layoutIfNeeded() } }
    • @vahid-amiri 辉煌。谢谢。这到底是怎么学的!!对我来说还有很长的路要走:)
    • 它适用于垂直滚动吗?水平滚动没问题
    • 这个解决方案也对我有用,尽管你会想要在你实现这个扩展的collectionView上禁用Paging Enabled。启用分页后,行为是不可预测的。我相信这是因为自动分页功能干扰了手动计算。
    • 如果我在滚动到最后一个单元格后每个屏幕显示 3 个单元格(1 个位于左右 2 个单元格的中间一半),则最后一个单元格不显示
    【解决方案3】:

    根据滚动速度捕捉到最近的单元格。

    没有任何故障。

    import UIKit
    
    class SnapCenterLayout: UICollectionViewFlowLayout {
      override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) }
        let parent = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
    
        let itemSpace = itemSize.width + minimumInteritemSpacing
        var currentItemIdx = round(collectionView.contentOffset.x / itemSpace)
    
        // Skip to the next cell, if there is residual scrolling velocity left.
        // This helps to prevent glitches
        let vX = velocity.x
        if vX > 0 {
          currentItemIdx += 1
        } else if vX < 0 {
          currentItemIdx -= 1
        }
    
        let nearestPageOffset = currentItemIdx * itemSpace
        return CGPoint(x: nearestPageOffset,
                       y: parent.y)
      }
    }
    

    【讨论】:

    • 这是迄今为止最好的方法。即使是小的接触,它也尊重速度。对于使用contentInset 的项目,请确保AddRemove 使用nearestPageOffset var.
    • @NSPunk 同意...有很多流行的应用程序具有类似的设计,它们都有这个奇怪的故障,当仍然有速度,但不足以捕捉到下一个单元格时。
    • 我必须将 minimumLineSpacing 添加到 itemSpace 以使其正确。干得好!
    • 这不能正确反映itemSize。这种方法只能直接在layout上设置itemSize,但是如果itemSize是通过UICollectionViewDelegateFlowLayout设置的,那么这个新设置的值就无法识别
    • @gondo 有什么改进的建议吗?
    【解决方案4】:

    这里值得我用一个简单的计算(快速):

    func snapToNearestCell(_ collectionView: UICollectionView) {
        for i in 0..<collectionView.numberOfItems(inSection: 0) {
    
            let itemWithSpaceWidth = collectionViewFlowLayout.itemSize.width + collectionViewFlowLayout.minimumLineSpacing
            let itemWidth = collectionViewFlowLayout.itemSize.width
    
            if collectionView.contentOffset.x <= CGFloat(i) * itemWithSpaceWidth + itemWidth / 2 {                
                let indexPath = IndexPath(item: i, section: 0)
                collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
                break
            }
        }
    }
    

    在需要的地方打电话。我叫它

    func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        snapToNearestCell(scrollView)
    }
    

    func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
        snapToNearestCell(scrollView)
    }
    

    collectionViewFlowLayout 的来源:

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        // Set up collection view
        collectionViewFlowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    }
    

    【讨论】:

    • 谢谢!在我看来,这是最干净、最简单的答案
    • 没问题。由于计算错误,我实际上在拥有许多单元格时遇到了一些问题。所以更新了我的答案。
    • collectionViewFlowLayout 来自哪里
    • 这个解决方案有效,但感觉不是原生的,因为它在完全减速之前开始捕捉。
    【解决方案5】:

    SWIFT 3 版@Iowa15 回复

    func scrollToNearestVisibleCollectionViewCell() {
        let visibleCenterPositionOfScrollView = Float(collectionView.contentOffset.x + (self.collectionView!.bounds.size.width / 2))
        var closestCellIndex = -1
        var closestDistance: Float = .greatestFiniteMagnitude
        for i in 0..<collectionView.visibleCells.count {
            let cell = collectionView.visibleCells[i]
            let cellWidth = cell.bounds.size.width
            let cellCenter = Float(cell.frame.origin.x + cellWidth / 2)
    
            // Now calculate closest cell
            let distance: Float = fabsf(visibleCenterPositionOfScrollView - cellCenter)
            if distance < closestDistance {
                closestDistance = distance
                closestCellIndex = collectionView.indexPath(for: cell)!.row
            }
        }
        if closestCellIndex != -1 {
            self.collectionView!.scrollToItem(at: IndexPath(row: closestCellIndex, section: 0), at: .centeredHorizontally, animated: true)
        }
    }
    

    需要在UIScrollViewDelegate中实现:

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        scrollToNearestVisibleCollectionViewCell()
    }
    
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            scrollToNearestVisibleCollectionViewCell()
        }
    }
    

    【讨论】:

    • 这对我很有用。我要补充一点,如果你在某个时候设置collectionView.decelerationRate = UIScrollViewDecelerationRateFast,你会更接近 App Store 的“感觉”。我还要补充一点,第 4 行的 FLT_MAX 应更改为 Float.greatestFiniteMagnitude,以避免 Xcode 警告。
    【解决方案6】:

    这是我的实现

    func snapToNearestCell(scrollView: UIScrollView) {
         let middlePoint = Int(scrollView.contentOffset.x + UIScreen.main.bounds.width / 2)
         if let indexPath = self.cvCollectionView.indexPathForItem(at: CGPoint(x: middlePoint, y: 0)) {
              self.cvCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
         }
    }
    

    像这样实现你的滚动视图代理

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        self.snapToNearestCell(scrollView: scrollView)
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        self.snapToNearestCell(scrollView: scrollView)
    }
    

    另外,为了更好地捕捉

    self.cvCollectionView.decelerationRate = UIScrollViewDecelerationRateFast
    

    像魅力一样工作

    【讨论】:

    • 您忘记将第一个代码 sn-p 放入一个名为“snapToNearestCell(scrollView: UIScrollView)”的函数中
    【解决方案7】:

    如果您想要简单的原生行为,无需自定义:

    collectionView.pagingEnabled = YES;
    

    这只有在集合视图布局项的大小都是一个大小并且UICollectionViewCellclipToBounds 属性设置为YES 时才能正常工作。

    【讨论】:

    • 这并没有给出最好的反馈,而且它也不能与具有多行的集合视图正常工作,但它确实很容易做到。
    • 如果你使用了这个并且你的单元格没有居中,请确保为你的布局设置minimumLineSpacing为0
    【解决方案8】:

    我尝试了@Mark Bourke 和@mrcrowley 两种解决方案,但它们给出的结果几乎相同,但具有不需要的粘性效果。

    考虑到velocity,我设法解决了这个问题。这是完整的代码。

    final class BetterSnappingLayout: UICollectionViewFlowLayout {
    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        guard let collectionView = collectionView else {
            return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
        }
    
        var offsetAdjusment = CGFloat.greatestFiniteMagnitude
        let horizontalCenter = proposedContentOffset.x + (collectionView.bounds.width / 2)
    
        let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
        let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)
        layoutAttributesArray?.forEach({ (layoutAttributes) in
            let itemHorizontalCenter = layoutAttributes.center.x
    
            if abs(itemHorizontalCenter - horizontalCenter) < abs(offsetAdjusment) {
                if abs(velocity.x) < 0.3 { // minimum velocityX to trigger the snapping effect
                    offsetAdjusment = itemHorizontalCenter - horizontalCenter
                } else if velocity.x > 0 {
                    offsetAdjusment = itemHorizontalCenter - horizontalCenter + layoutAttributes.bounds.width
                } else { // velocity.x < 0
                    offsetAdjusment = itemHorizontalCenter - horizontalCenter - layoutAttributes.bounds.width
                }
            }
        })
    
        return CGPoint(x: proposedContentOffset.x + offsetAdjusment, y: proposedContentOffset.y)
    }
    

    }

    【讨论】:

    • 在将 collectionView.decelerationRate = UIScrollView.DecelerationRate.fast 添加到 collectionView 本身时,这对我来说效果最好
    • 我登录只是为了对此表示赞同并说:这是迄今为止页面上最好的解决方案,并且立即按我希望的方式工作。我花了太长时间来调整投票最多的例子。谢谢仁!
    • 是的,这似乎可以。
    【解决方案9】:

    得到了来自 SO 帖子 here 和文档 here 的答案

    首先你可以做的是通过让你的类成为滚动视图委托来设置你的集合视图的滚动视图的委托你的类

    MyViewController : SuperViewController<... ,UIScrollViewDelegate>
    

    然后将您的视图控制器设置为委托

    UIScrollView *scrollView = (UIScrollView *)super.self.collectionView;
    scrollView.delegate = self;
    

    或者在界面构建器中通过 control + shift 单击您的集合视图然后控制 + 拖动或右键单击拖动到您的视图控制器并选择委托。 (你应该知道如何做到这一点)。 这行不通。 UICollectionView 是 UIScrollView 的子类,因此您现在可以通过 control + shift 点击在界面构建器中看到它

    接下来实现委托方法- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

    MyViewController.m
    
    ... 
    
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    {
    
    }
    

    文档声明:

    参数

    滚动视图 |正在减速滚动的滚动视图对象 的内容视图。

    讨论 滚动视图在滚动时调用该方法 运动停止。 UIScrollView的减速属性 控制减速。

    可用性适用于 iOS 2.0 及更高版本。

    然后在该方法内部检查哪个单元格在停止滚动时最接近滚动视图的中心

    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    {
      //NSLog(@"%f", truncf(scrollView.contentOffset.x + (self.pictureCollectionView.bounds.size.width / 2)));
    
    float visibleCenterPositionOfScrollView = scrollView.contentOffset.x + (self.pictureCollectionView.bounds.size.width / 2);
    
    //NSLog(@"%f", truncf(visibleCenterPositionOfScrollView / imageArray.count));
    
    
    NSInteger closestCellIndex;
    
    for (id item in imageArray) {
        // equation to use to figure out closest cell
        // abs(visibleCenter - cellCenterX) <= (cellWidth + cellSpacing/2)
    
        // Get cell width (and cell too)
        UICollectionViewCell *cell = (UICollectionViewCell *)[self collectionView:self.pictureCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathWithIndex:[imageArray indexOfObject:item]]];
        float cellWidth = cell.bounds.size.width;
    
        float cellCenter = cell.frame.origin.x + cellWidth / 2;
    
        float cellSpacing = [self collectionView:self.pictureCollectionView layout:self.pictureCollectionView.collectionViewLayout minimumInteritemSpacingForSectionAtIndex:[imageArray indexOfObject:item]];
    
        // Now calculate closest cell
    
        if (fabsf(visibleCenterPositionOfScrollView - cellCenter) <= (cellWidth + (cellSpacing / 2))) {
            closestCellIndex = [imageArray indexOfObject:item];
            break;
        }
    }
    
    if (closestCellIndex != nil) {
    
    [self.pictureCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathWithIndex:closestCellIndex] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
    
    // This code is untested. Might not work.
    
    }
    

    【讨论】:

    • 我现在正在做粗略的计算,大约 10 分钟后我会用一个基本的工作计算来编辑。
    • 如果你正在使用多个集合视图并且你已经设置了一个 viewController 作为它的委托,你可以简单地检查 scrollView 和 collectionView,因为 collectionView 继承自 scrollView。这是为了找出哪个collectionView已经停止滚动。
    • @Minebomber 嘿,伙计,刚刚试了一下,当我尝试在 for 循环中获取 cellForItemAtIndexPath 时,我不断收到错误的访问错误。我很惊讶出现了一个错误,因为我认为使用 collectionView,你总是可以保证一个单元格。
    • 是的,所以在实际委托中显式调用委托回调是个坏主意。
    • 所以我最终遍历了 collectionView.visibleCells 来解决我的问题。我仍然最终手动调用委托方法来选择单元格...
    【解决方案10】:

    您也可以尝试修改上述答案:

    -(void)scrollToNearestVisibleCollectionViewCell {
        float visibleCenterPositionOfScrollView = _collectionView.contentOffset.x + (self.collectionView.bounds.size.width / 2);
    
        NSInteger closestCellIndex = -1;
        float closestDistance = FLT_MAX;
        for (int i = 0; i < _collectionView.visibleCells.count; i++) {
            UICollectionViewCell *cell = _collectionView.visibleCells[i];
            float cellWidth = cell.bounds.size.width;
    
            float cellCenter = cell.frame.origin.x + cellWidth / 2;
    
            // Now calculate closest cell
            float distance = fabsf(visibleCenterPositionOfScrollView - cellCenter);
            if (distance < closestDistance) {
                closestDistance = distance;
                closestCellIndex = [_collectionView indexPathForCell:cell].row;
            }
        }
    
        if (closestCellIndex != -1) {
            [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:closestCellIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
        }
    }
    

    【讨论】:

      【解决方案11】:

      我刚刚找到了我认为是解决这个问题的最佳方法:

      首先给collectionView已经存在的gestureRecognizer添加一个target:

      [self.collectionView.panGestureRecognizer addTarget:self action:@selector(onPan:)];
      

      让选择器指向一个以 UIPanGestureRecognizer 作为参数的方法:

      - (void)onPan:(UIPanGestureRecognizer *)recognizer {};
      

      然后在这个方法中,当平移手势结束时强制collectionView滚动到合适的单元格。 我通过从集合视图中获取可见项目并根据平移的方向确定要滚动到哪个项目来做到这一点。

      if (recognizer.state == UIGestureRecognizerStateEnded) {
      
              // Get the visible items
              NSArray<NSIndexPath *> *indexes = [self.collectionView indexPathsForVisibleItems];
              int index = 0;
      
              if ([(UIPanGestureRecognizer *)recognizer velocityInView:self.view].x > 0) {
                  // Return the smallest index if the user is swiping right
                  for (int i = index;i < indexes.count;i++) {
                      if (indexes[i].row < indexes[index].row) {
                          index = i;
                      }
                  }
              } else {
                  // Return the biggest index if the user is swiping left
                  for (int i = index;i < indexes.count;i++) {
                      if (indexes[i].row > indexes[index].row) {
                          index = i;
                      }
                  }
              }
              // Scroll to the selected item
              [self.collectionView scrollToItemAtIndexPath:indexes[index] atScrollPosition:UICollectionViewScrollPositionLeft animated:YES];
          }
      

      请记住,在我的情况下,一次只能看到两个项目。不过,我确信这种方法可以适用于更多项目。

      【讨论】:

        【解决方案12】:

        这来自 2012 年 WWDC 视频,用于 Objective-C 解决方案。我将 UICollectionViewFlowLayout 子类化并添加了以下内容。

        -(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
            {
                CGFloat offsetAdjustment = MAXFLOAT;
                CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2);
        
                CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
                NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
        
                for (UICollectionViewLayoutAttributes *layoutAttributes in array)
                {
                    CGFloat itemHorizontalCenter = layoutAttributes.center.x;
                    if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offsetAdjustment))
                    {
                        offsetAdjustment = itemHorizontalCenter - horizontalCenter;
                    }
                }
        
                return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
            }
        

        我提出这个问题的原因是为了以一种原生的感觉捕捉,这是我从 Mark 接受的答案中得到的……我把它放在了 collectionView 的视图控制器中。

        collectionView.decelerationRate = UIScrollViewDecelerationRateFast;
        

        【讨论】:

          【解决方案13】:

          此解决方案提供了更好、更流畅的动画效果。

          斯威夫特 3

          要使第一个和最后一个项目居中添加插图:

          func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
          
              return UIEdgeInsetsMake(0, cellWidth/2, 0, cellWidth/2)
          }
          

          然后使用scrollViewWillEndDragging方法中的targetContentOffset来改变结束位置。

          func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
          
              let numOfItems = collectionView(mainCollectionView, numberOfItemsInSection:0)
              let totalContentWidth = scrollView.contentSize.width + mainCollectionViewFlowLayout.minimumInteritemSpacing - cellWidth
              let stopOver = totalContentWidth / CGFloat(numOfItems)
          
              var targetX = round((scrollView.contentOffset.x + (velocity.x * 300)) / stopOver) * stopOver
              targetX = max(0, min(targetX, scrollView.contentSize.width - scrollView.frame.width))
          
              targetContentOffset.pointee.x = targetX
          }
          

          也许在您的情况下,totalContentWidth 的计算方式不同,例如没有minimumInteritemSpacing,所以相应地调整它。 您也可以使用velocity 中使用的300

          附:确保类采用UICollectionViewDataSource 协议

          【讨论】:

          • 嘿,罗兰,生活怎么样?刚刚遇到了你对这个问题的回答......很老,但仍然很好,谢谢!仅供参考,如果您的对象采用UICollectionViewDataSource,则只能以这种方式使用collectionView(mainCollectionView, numberOfItemsInSection:0) 方法。你为什么要从scrollView.contentSize.width中减去cellWidth,总宽度不总是scrollView.contentSize.width吗?
          • 嘿,保罗!在我的情况下,需要减去 cellWidth 来抵消它,使其居中。也许在您的情况下,totalContentWidth 的计算方式不同。
          【解决方案14】:

          我一直在通过在 uicollectionview 的属性检查器上设置“启用分页”来解决此问题。

          对我来说,当单元格的宽度与 uicollectionview 的宽度相同时会发生这种情况。

          不涉及编码。

          【讨论】:

            【解决方案15】:

            这是一个 Swift 3.0 版本,根据上面 Mark 的建议,它应该适用于水平和垂直方向:

              override func targetContentOffset(
                forProposedContentOffset proposedContentOffset: CGPoint,
                withScrollingVelocity velocity: CGPoint
              ) -> CGPoint {
            
                guard
                  let collectionView = collectionView
                else {
                  return super.targetContentOffset(
                    forProposedContentOffset: proposedContentOffset,
                    withScrollingVelocity: velocity
                  )
                }
            
                let realOffset = CGPoint(
                  x: proposedContentOffset.x + collectionView.contentInset.left,
                  y: proposedContentOffset.y + collectionView.contentInset.top
                )
            
                let targetRect = CGRect(origin: proposedContentOffset, size: collectionView.bounds.size)
            
                var offset = (scrollDirection == .horizontal)
                  ? CGPoint(x: CGFloat.greatestFiniteMagnitude, y:0.0)
                  : CGPoint(x:0.0, y:CGFloat.greatestFiniteMagnitude)
            
                offset = self.layoutAttributesForElements(in: targetRect)?.reduce(offset) {
                  (offset, attr) in
                  let itemOffset = attr.frame.origin
                  return CGPoint(
                    x: abs(itemOffset.x - realOffset.x) < abs(offset.x) ? itemOffset.x - realOffset.x : offset.x,
                    y: abs(itemOffset.y - realOffset.y) < abs(offset.y) ? itemOffset.y - realOffset.y : offset.y
                  )
                } ?? .zero
            
                return CGPoint(x: proposedContentOffset.x + offset.x, y: proposedContentOffset.y + offset.y)
              }
            

            【讨论】:

            • 知道为什么这会改变我的收藏视图单元格大小并使我的收藏视图设置为水平、垂直吗?
            【解决方案16】:

            斯威夫特 4.2。简单的。对于固定的 itemSize。水平流向。

            func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
            
                if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
                    let floatingPage = targetContentOffset.pointee.x/scrollView.bounds.width
                    let rule: FloatingPointRoundingRule = velocity.x > 0 ? .up : .down
                    let page = CGFloat(Int(floatingPage.rounded(rule)))
                    targetContentOffset.pointee.x = page*(layout.itemSize.width + layout.minimumLineSpacing)
                }
            
            }
            

            【讨论】:

            • 当您向左滚动并立即单击时(滚动动画仍在播放),这会产生 jumping to previous cell 的不幸效果。你会看到滚动被“取消”并跳转到上一个单元格。
            最近更新 更多