NSCollectionViewCompositionalLayout是否支持动态增高单元格?
嘿,这个需求完全可以实现!我之前做类似功能的时候踩过一些坑,给你分享两个靠谱的方案:
方案一:通过
performBatchUpdates结合动态尺寸计算 这是最常用且简洁的实现方式,利用CompositionalLayout的estimated尺寸特性来实现平滑的高度过渡动画。
- 维护高度状态
在你的数据源里添加一个数组,用来存储每个单元格的当前高度,初始值设为你要的固定高度:
var itemHeights: [CGFloat] = Array(repeating: 80, count: yourItemCount)
- 创建支持动态尺寸的CompositionalLayout
把item的高度设置为estimated类型,让布局允许动态调整尺寸:
func createLayout() -> NSCollectionViewLayout { // 单元格尺寸:宽度占满,高度用estimated默认值 let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(80)) let item = NSCollectionLayoutItem(layoutSize: itemSize) // 组尺寸和单元格一致(单列布局) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(80)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.interGroupSpacing = 8 // 可选:设置单元格间距 return NSCollectionViewCompositionalLayout(section: section) }
- 实现动态尺寸计算
添加代理方法,根据数据源的高度状态返回对应尺寸:
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { return NSSize(width: collectionView.bounds.width, height: itemHeights[indexPath.item]) }
- 触发高度更新并带动画
当外部事件触发时(比如单元格选中、图片下载完成),更新高度值并调用批量更新方法:
func updateCellHeight(at indexPath: IndexPath, newHeight: CGFloat) { // 确保在主线程执行UI操作 DispatchQueue.main.async { self.itemHeights[indexPath.item] = newHeight self.collectionView.performBatchUpdates({ self.collectionView.reloadItems(at: [indexPath]) }, completion: { _ in // 动画完成后的回调(可选) }) } }
方案二:自定义布局属性手动控制动画
如果方案一的动画效果不符合预期,你可以通过自定义布局属性来更精细地控制动画过程:
- 自定义布局属性类
重写copy和isEqual方法,确保布局属性变化能被正确识别:
class CustomLayoutAttributes: NSCollectionViewLayoutAttributes { override func copy(with zone: NSZone? = nil) -> Any { let copy = super.copy(with: zone) as! CustomLayoutAttributes copy.size = self.size return copy } override func isEqual(_ object: Any?) -> Bool { guard let other = object as? CustomLayoutAttributes else { return false } return super.isEqual(object) && other.size == self.size } }
- 自定义CompositionalLayout
继承基础布局类,重写布局属性获取方法:
class CustomCompositionalLayout: NSCollectionViewCompositionalLayout { override func layoutAttributesForItem(at indexPath: IndexPath) -> NSCollectionViewLayoutAttributes? { let attributes = super.layoutAttributesForItem(at: indexPath) as? CustomLayoutAttributes ?? CustomLayoutAttributes(forCellWith: indexPath) // 从数据源获取当前高度 attributes.size = NSSize(width: collectionView?.bounds.width ?? 0, height: itemHeights[indexPath.item]) return attributes } override func invalidationContext(forItemAt indexPath: IndexPath) -> NSCollectionViewLayoutInvalidationContext { let context = super.invalidationContext(forItemAt: indexPath) context.invalidateItems(at: [indexPath]) return context } }
- 触发带动画的布局更新
使用NSAnimationContext来包裹布局刷新,实现自定义时长的动画:
func updateCellHeight(at indexPath: IndexPath, newHeight: CGFloat) { DispatchQueue.main.async { self.itemHeights[indexPath.item] = newHeight NSAnimationContext.runAnimationGroup({ context in context.duration = 0.3 // 自定义动画时长 self.collectionView.collectionViewLayout.invalidateLayout() }, completionHandler: nil) } }
注意事项
- 确保你的单元格视图使用Auto Layout约束,让内容能自适应高度变化;
- 图片下载完成后更新高度时,一定要切换到主线程执行UI操作;
- 使用
estimated尺寸时,布局会自动调整其他单元格的位置,避免出现布局混乱。
内容的提问来源于stack exchange,提问作者AirXygène
相关产品推荐
相关产品推荐

