Swift CompositionalLayout:CollectionView单元格高度未按图片高度计算
解决CompositionalLayout+DiffableDataSource下相册图片单元格高度自适应失效问题
问题分析
当前实现中,初始状态单元格高度固定为预估的200pt,仅滚动后部分单元格能正确调整高度,核心原因有三点:
PhotoThumbnailContentView的intrinsicContentSize硬编码返回固定高度,布局系统无法根据图片动态计算尺寸- 图片异步加载完成后,未通知布局系统重新计算单元格高度
- 未利用PHAsset的像素宽高提前计算高度,导致初始布局只能依赖预估高度
解决方案
方案1:动态更新ContentView的内在尺寸
修改PhotoThumbnailContentView的intrinsicContentSize,根据图片比例动态返回高度,同时在图片加载完成后通知布局系统更新。
- 更新
intrinsicContentSize计算逻辑:
class PhotoThumbnailContentView : UIView, UIContentView { // ... 其他代码 ... override var intrinsicContentSize: CGSize { guard let image = imageView.image else { // 无图片时用预估高度 return CGSize(width: UIView.noIntrinsicMetric, height: 200) } // 根据图片比例计算高度,宽度铺满屏幕 let ratio = image.size.height / image.size.width let screenWidth = UIScreen.main.bounds.width return CGSize(width: screenWidth, height: screenWidth * ratio) } // ... 其他代码 ... func configure(configuration: UIContentConfiguration) { guard let configuration = configuration as? PhotoThumbnailCellConfiguration else { return } imageView.image = configuration.image imageView.contentMode = configuration.contentMode // 通知布局系统内在尺寸已变化 invalidateIntrinsicContentSize() // 触发父视图布局更新 superview?.superview?.layoutIfNeeded() } }
方案2:提前利用PHAsset像素尺寸计算高度
直接通过PHAsset的pixelWidth和pixelHeight计算单元格高度,让布局系统初始就能拿到精确值,无需等待图片加载。
- 在ViewController中添加高度缓存:
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { // ... 其他代码 ... var assetHeightCache: [String: CGFloat] = [:] // ... 其他代码 ... }
- 在cell注册时计算并缓存高度:
func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<PhotoThumbnailCollectionViewCell, PHAsset> { [weak self] cell, indexPath, asset in guard let self = self else { return } let scale = UIScreen.main.scale cell.contentMode = .scaleAspectFit // 计算并缓存单元格高度 let collectionWidth = self.collectionView.frame.width let ratio = CGFloat(asset.pixelHeight) / CGFloat(asset.pixelWidth) let cellHeight = collectionWidth * ratio self.assetHeightCache[asset.localIdentifier] = cellHeight // ... 图片加载代码 ... } // ... 其他代码 ... }
- 实现UICollectionViewDelegateFlowLayout方法返回精确尺寸:
extension ViewController { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let asset = fetchResult.object(at: indexPath.row) let width = collectionView.frame.width let height = assetHeightCache[asset.localIdentifier] ?? 200 return CGSize(width: width, height: height) } }
方案3:图片加载完成后重载对应单元格
在图片异步加载完成后,重载当前单元格,强制布局系统重新计算高度。
修改图片加载的resultHandler:
self.imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: cell.contentMode == .scaleAspectFit ? .aspectFit : .aspectFill, options: nil, resultHandler: { [weak self] image, _ in guard let self = self else { return } if cell.representedAssetIdentifier == asset.localIdentifier { cell.image = image // 获取单元格的indexPath并重载 if let indexPath = self.collectionView.indexPath(for: cell) { self.collectionView.reloadItems(at: [indexPath]) } } })
最佳实践推荐
优先选择方案2,因为它不需要等待图片加载就能确定单元格高度,初始布局就完全准确,避免了滚动时的高度跳动问题,同时性能更优。
内容的提问来源于stack exchange,提问作者J. Coder
相关产品推荐
相关产品推荐

