Swift CollectionView 单元格滑动吸附功能无法回到首个单元格问题
UICollectionView 吸附对齐无法选中首个单元格问题修复
问题根因
现有逻辑仅通过比对滚动视图中心与可见单元格中心的距离判断吸附目标,在大屏设备上滑动到最左侧时,第0个和第1个单元格同时处于可见区域,此时滚动视图中心坐标距离第1个单元格的中心更近,因此永远无法吸附到第0个单元格。iPhone 12因为屏幕宽度和单元格适配的巧合,刚好不会触发该边界场景,所以运行正常。
修复后的扩展代码
extension UICollectionView { func scrollToNearestVisibleCollectionViewCell() { decelerationRate = UIScrollView.DecelerationRate.normal // 左边界特殊判断:如果已经滑到接近最左,直接选中第一个单元格,阈值可根据实际需求调整 let leftThreshold: CGFloat = (bounds.size.width / 2) * 0.5 if contentOffset.x <= leftThreshold { scrollToItem(at: IndexPath(row: 0, section: 0), at: .centeredHorizontally, animated: true) return } // 右边界特殊判断:如果已经滑到接近最右,直接选中最后一个单元格 let rightThreshold = contentSize.width - bounds.size.width + leftThreshold if contentOffset.x >= rightThreshold { let lastRow = numberOfItems(inSection: 0) - 1 scrollToItem(at: IndexPath(row: lastRow, section: 0), at: .centeredHorizontally, animated: true) return } // 原有中间位置逻辑保留 let visibleCenterPositionOfScrollView = Float(self.contentOffset.x + (self.bounds.size.width / 2)) var closestCellIndex = -1 var closestDistance: Float = .greatestFiniteMagnitude for i in 0..<visibleCells.count { let cell = visibleCells[i] let cellWidth = cell.bounds.size.width let cellCenter = Float(cell.frame.origin.x + cellWidth / 2) let distance: Float = fabsf(visibleCenterPositionOfScrollView - cellCenter) if distance < closestDistance { closestDistance = distance closestCellIndex = self.indexPath(for: cell)!.row } } if closestCellIndex != -1 { scrollToItem(at: IndexPath(row: closestCellIndex, section: 0), at: .centeredHorizontally, animated: true) } } }
调用代码无需修改
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.collectionView.scrollToNearestVisibleCollectionViewCell() setPageByScrollOffset(scrollView: scrollView) } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { collectionView.scrollToNearestVisibleCollectionViewCell() } }
可选优化方案
如果你的单元格是固定等宽+固定间距的布局,可以直接通过偏移量计算目标index,不需要遍历可见单元格,性能更稳定:
// 等宽单元格场景优化版逻辑替换原有遍历逻辑 let itemWidth = 你的单元格宽度 let itemSpacing = 你的单元格间距 let targetIndex = round((contentOffset.x + bounds.size.width / 2) / (itemWidth + itemSpacing)) let finalIndex = max(0, min(numberOfItems(inSection: 0)-1, Int(targetIndex))) scrollToItem(at: IndexPath(row: finalIndex, section: 0), at: .centeredHorizontally, animated: true)
内容的提问来源于stack exchange,提问作者Karl Mcgeough
相关产品推荐
相关产品推荐

