水平滚动时,UICollectionView会捕捉到单元格

Mar*_*rke 53 objective-c uiscrollview ios uicollectionview

我知道有些人之前已经问过这个问题,但他们都是关于UITableViews或者UIScrollViews我无法获得为我工作的公认解决方案.我想要的是在UICollectionView水平滚动时的捕捉效果- 就像iOS AppStore中发生的那样.iOS 9+是我的目标版本,因此请在回答之前先查看UIKit的更改.

谢谢.

Mar*_*rke 93

虽然最初我使用的是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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

collectionView?.decelerationRate = UIScrollViewDecelerationRateFast

  • 由于某种原因,这导致我的集合视图是垂直的,并调整我的所有单元格,任何见解? (3认同)
  • 对于设置了“ sectionInset.left”的用户,请将return语句替换为:“ return CGPoint(x:proposalContentOffset.x + offsetAdjustment-sectionInset.left,y:proposalContentOffset.y)”。 (3认同)
  • 这个解决方案虽然可以捕捉单元格,但它的动画非常有问题,因为它不考虑速度。如果用户滑动得非常快,您会发现它出现了很多故障。 (2认同)
  • 我需要在滑动时始终捕捉到下一个单元格,因此我通过检查滑动方向来对其进行了一些调整:let itemOffset = layoutAttributes.frame.origin.x中的layoutAttributesArray?.forEach({(layoutAttributes))let itemWidth = Float(layoutAttributes.frame.width)的方向:Float = velocity.x&gt; 0?1:如果fabsf(Float(itemOffset-horizo​​ntalOffset))&lt;fabsf(Float(offsetAdjustment))+ itemWidth *方向{offsetAdjustment = itemOffset- horizo​​ntalOffset}})` (2认同)

Nam*_*Nam 24

这里有什么值得我使用的简单计算(在swift中):

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

打电话到你需要的地方.我打电话给它

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    snapToNearestCell(scrollView)
}
Run Code Online (Sandbox Code Playgroud)

func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    snapToNearestCell(scrollView)
}
Run Code Online (Sandbox Code Playgroud)

collectionViewFlowLayout可能来自:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    // Set up collection view
    collectionViewFlowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
}
Run Code Online (Sandbox Code Playgroud)


Vah*_*iri 22

根据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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您需要UIScrollViewDelegate为集合视图实现协议,然后添加以下两种方法:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    self.collectionView.scrollToNearestVisibleCollectionViewCell()
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
        self.collectionView.scrollToNearestVisibleCollectionViewCell()
    }
}
Run Code Online (Sandbox Code Playgroud)


Ric*_*hiy 21

遵循滚动速度,捕捉到最近的单元格。

正常工作。

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)
  }
}
Run Code Online (Sandbox Code Playgroud)


Met*_*tte 18

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

需要在UIScrollViewDelegate中实现:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    scrollToNearestVisibleCollectionViewCell()
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
        scrollToNearestVisibleCollectionViewCell()
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这对我很有用.我想补充说,如果你在某个时候设置`collectionView.decelerationRate = UIScrollViewDecelerationRateFast`,你会更接近App Store"感觉".我还要补充一点,第4行的`FLT_MAX`应该改为`Float.greatestFiniteMagnitude`,以避免Xcode警告. (2认同)

Sou*_*dra 10

这是我的实现

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)
     }
}
Run Code Online (Sandbox Code Playgroud)

像这样实现滚动视图委托

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

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    self.snapToNearestCell(scrollView: scrollView)
}
Run Code Online (Sandbox Code Playgroud)

另外,为了更好地捕捉

self.cvCollectionView.decelerationRate = UIScrollViewDecelerationRateFast
Run Code Online (Sandbox Code Playgroud)

奇迹般有效


Nho*_*yen 10

我尝试了@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)
}
Run Code Online (Sandbox Code Playgroud)

}


NSD*_*yer 8

如果您想要简单的本机行为,而无需自定义:

collectionView.pagingEnabled = YES;
Run Code Online (Sandbox Code Playgroud)

仅当集合视图布局项目的大小全部为一种大小且UICollectionViewCellclipToBounds属性设置为时,此方法才能正常工作YES