自定义UICollectionViewFlowLayout如何检测UICollectionView的更新或重加载
自定义UICollectionViewFlowLayout减少冗余布局计算的方案
核心优化思路是通过缓存+捕获变更事件+增量计算的方式,避免每次prepare都全量计算所有cell的frame:
- 第一步:添加布局属性缓存
给自定义FlowLayout添加缓存属性,存储已经计算完成的cell布局属性,避免重复计算:
// 缓存已计算的cell布局属性,key为IndexPath private var cachedItemAttributes: [IndexPath: UICollectionViewLayoutAttributes] = [:] // 缓存上次的数据源数量,用于快速判断是否需要全量重算 private var lastItemCount: Int = 0
- 第二步:捕获布局失效事件,按需清理缓存
重写invalidateLayout(with:)方法,区分全量重载和局部更新场景,只清理受影响的缓存:
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) { super.invalidateLayout(with: context) // 全量布局失效场景:调用reloadData、数据源数量变化 if context.invalidateEverything || context.invalidateDataSourceCounts { cachedItemAttributes.removeAll() lastItemCount = 0 return } // 局部更新场景:只清理被标记为失效的IndexPath对应缓存 if let invalidIndexPaths = context.invalidatedItemIndexPaths { invalidIndexPaths.forEach { cachedItemAttributes.removeValue(forKey: $0) } } }
- 第三步:增量计算布局
在prepare()方法中,只计算缓存中不存在的cell的布局属性,已经缓存的直接复用:
override func prepare() { super.prepare() guard let collectionView = collectionView else { return } let currentItemCount = collectionView.numberOfItems(inSection: 0) // 数据源数量无变化且已有缓存,跳过全量计算 guard currentItemCount != lastItemCount || cachedItemAttributes.isEmpty else { return } lastItemCount = currentItemCount // 遍历所有IndexPath,只计算未缓存的布局 for item in 0..<currentItemCount { let indexPath = IndexPath(item: item, section: 0) guard cachedItemAttributes[indexPath] == nil else { continue } let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) // 替换为原有frame计算逻辑 attributes.frame = // 计算得到的frame cachedItemAttributes[indexPath] = attributes } }
- 第四步:捕获增量更新事件(可选优化)
如果需要更精细化的处理插入、删除、移动等增量更新,还可以重写prepare(forCollectionViewUpdates:)方法,提前标记需要更新的IndexPath:
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) { super.prepare(forCollectionViewUpdates: updateItems) for update in updateItems { switch update.updateAction { case .insert, .delete, .move: [update.indexPathBeforeUpdate, update.indexPathAfterUpdate] .compactMap { $0 } .forEach { cachedItemAttributes.removeValue(forKey: $0) } default: break } } }
额外优化建议:如果cell的布局依赖自身内容大小,建议将内容尺寸计算逻辑提前放到数据源层缓存,不要放到布局计算阶段执行,能进一步降低布局耗时。如果cell数量极多,还可以只计算当前可视区域内的cell布局,非可视区域的cell等到滚动到对应区域时再计算。
内容的提问来源于stack exchange,提问作者JoShin
相关产品推荐
相关产品推荐

