You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift CompositionalLayout:CollectionView单元格高度未按图片高度计算

解决CompositionalLayout+DiffableDataSource下相册图片单元格高度自适应失效问题

问题分析

当前实现中,初始状态单元格高度固定为预估的200pt,仅滚动后部分单元格能正确调整高度,核心原因有三点:

  1. PhotoThumbnailContentView的intrinsicContentSize硬编码返回固定高度,布局系统无法根据图片动态计算尺寸
  2. 图片异步加载完成后,未通知布局系统重新计算单元格高度
  3. 未利用PHAsset的像素宽高提前计算高度,导致初始布局只能依赖预估高度

解决方案

方案1:动态更新ContentView的内在尺寸

修改PhotoThumbnailContentView的intrinsicContentSize,根据图片比例动态返回高度,同时在图片加载完成后通知布局系统更新。

  1. 更新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计算单元格高度,让布局系统初始就能拿到精确值,无需等待图片加载。

  1. 在ViewController中添加高度缓存:
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    // ... 其他代码 ...
    var assetHeightCache: [String: CGFloat] = [:]
    // ... 其他代码 ...
}
  1. 在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
        
        // ... 图片加载代码 ...
    }
    // ... 其他代码 ...
}
  1. 实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 20:55:21