如何实现非滚动时UICollectionView选中单元格与邻格的额外间距?
实现UICollectionView选中单元格与相邻单元格的动态额外间距
要实现非滚动状态下选中单元格与相邻单元格的额外间距,确实需要自定义UICollectionViewFlowLayout子类,结合你已有的isScrollingInMotion状态标记来控制间距的应用时机,具体实现如下:
1. 自定义FlowLayout子类
创建继承自UICollectionViewFlowLayout的子类,用于动态调整选中项的相邻间距:
class CustomPhotoFlowLayout: UICollectionViewFlowLayout { var selectedIndexPath: IndexPath? var isScrollingInMotion = false let extraSpacing: CGFloat = 8 // 自定义额外间距值 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { // 复制原布局属性,避免修改系统缓存 guard let attributes = super.layoutAttributesForElements(in: rect)?.map({ $0.copy() as! UICollectionViewLayoutAttributes }), let selectedIndex = selectedIndexPath, !isScrollingInMotion else { return super.layoutAttributesForElements(in: rect) } // 获取选中项的布局属性 guard let selectedAttrs = attributes.first(where: { $0.indexPath == selectedIndex }) else { return attributes } // 调整前一个单元格的间距 let prevIndex = IndexPath(item: selectedIndex.item - 1, section: selectedIndex.section) if let prevAttrs = attributes.first(where: { $0.indexPath == prevIndex }) { prevAttrs.frame.origin.x = selectedAttrs.frame.origin.x - prevAttrs.frame.width - minimumInteritemSpacing - extraSpacing } // 调整后一个单元格的间距 let nextIndex = IndexPath(item: selectedIndex.item + 1, section: selectedIndex.section) if let nextAttrs = attributes.first(where: { $0.indexPath == nextIndex }) { nextAttrs.frame.origin.x = selectedAttrs.frame.origin.x + selectedAttrs.frame.width + minimumInteritemSpacing + extraSpacing } return attributes } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true // 确保滚动过程中布局能实时更新 } }
2. 替换ViewController中的布局实例
在setupCollectionView方法中使用自定义布局:
private func setupCollectionView() { let layout = CustomPhotoFlowLayout() layout.scrollDirection = .horizontal layout.minimumInteritemSpacing = itemSpacing let sideInset = (view.bounds.width - 44) / 2 // 保持首尾项居中 layout.sectionInset = UIEdgeInsets(top: 0, left: sideInset, bottom: 0, right: sideInset) collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) // ... 其他初始化代码 }
3. 同步状态并触发布局更新
在选中状态变化、滚动状态变化时,更新自定义布局的状态并触发重新布局:
// 选中单元格时更新布局状态 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let layout = collectionView.collectionViewLayout as? CustomPhotoFlowLayout else { return } layout.selectedIndexPath = indexPath layout.invalidateLayout() } // 滚动状态更新时同步布局状态 func scrollViewDidScroll(_ scrollView: UIScrollView) { isScrollingInMotion = true guard let layout = collectionView.collectionViewLayout as? CustomPhotoFlowLayout else { return } layout.isScrollingInMotion = true layout.invalidateLayout() } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { isScrollingInMotion = false guard let layout = collectionView.collectionViewLayout as? CustomPhotoFlowLayout else { return } layout.isScrollingInMotion = false layout.invalidateLayout() } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { isScrollingInMotion = false guard let layout = collectionView.collectionViewLayout as? CustomPhotoFlowLayout else { return } layout.isScrollingInMotion = false layout.invalidateLayout() } }
关键说明
extraSpacing可以根据需求调整为你需要的额外间距值- 复制布局属性是为了避免修改系统缓存的布局数据,防止出现异常布局
- 每次状态变化时调用
invalidateLayout()是确保布局能重新计算并应用新的间距规则
内容的提问来源于stack exchange,提问作者Eddie
相关产品推荐
相关产品推荐

