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

自定义相册CollectionView加载缓慢问题及优化咨询

解决相册照片加载到CollectionView时的卡顿问题

嘿,这个卡顿问题我之前帮不少开发者踩过坑,核心就是把那些耗时的宽高比计算从主线程挪走,同时避免重复计算。给你几个优先级从高到低的实用方案:

1. 直接利用PHAsset的内置尺寸,跳过图片加载计算

这是最立竿见影的优化!你根本不需要加载图片文件来获取宽高比——相册里的PHAsset本身就提供了pixelWidth和pixelHeight属性,直接用这两个值计算就行,省掉了大量的图片解码和内存开销。

示例代码:

// 获取对应的PHAsset
let asset = yourAssetArray[indexPath.item]
let aspectRatio = CGFloat(asset.pixelWidth) / CGFloat(asset.pixelHeight)
// 假设你要的item宽度是固定的(比如屏幕宽度/2)
let targetWidth = UIScreen.main.bounds.width / 2
let itemSize = CGSize(width: targetWidth, height: targetWidth / aspectRatio)

2. 缓存已计算的尺寸,避免重复计算

不管用哪种方式计算尺寸,都要把结果缓存起来,不然每次滚动或者刷新都会重复计算,浪费资源。可以用字典来存,键可以用PHAsset的localIdentifier(唯一标识)或者indexPath的字符串形式。

示例代码:

// 类里定义缓存字典
private var sizeCache: [String: CGSize] = [:]

// 计算尺寸时先查缓存
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let asset = yourAssetArray[indexPath.item]
    let key = asset.localIdentifier
    
    if let cachedSize = sizeCache[key] {
        return cachedSize
    }
    
    // 计算尺寸(用上面的PHAsset属性方法)
    let aspectRatio = CGFloat(asset.pixelWidth) / CGFloat(asset.pixelHeight)
    let targetWidth = UIScreen.main.bounds.width / 2
    let itemSize = CGSize(width: targetWidth, height: targetWidth / aspectRatio)
    
    // 存入缓存
    sizeCache[key] = itemSize
    return itemSize
}

注意:如果用户编辑了照片,记得清空对应asset的缓存值哦!

3. 异步预计算所有尺寸,主线程刷新布局

不要在sizeForItemAt这种主线程调用的方法里做计算,最好在初始化相册数据的时候,就把所有asset的尺寸在后台队列一次性计算完,存到缓存里,然后再刷新CollectionView。

示例代码:

// 加载相册数据后异步计算尺寸
DispatchQueue.global(qos: .userInitiated).async {
    for asset in self.yourAssetArray {
        let aspectRatio = CGFloat(asset.pixelWidth) / CGFloat(asset.pixelHeight)
        let targetWidth = UIScreen.main.bounds.width / 2
        let itemSize = CGSize(width: targetWidth, height: targetWidth / aspectRatio)
        self.sizeCache[asset.localIdentifier] = itemSize
    }
    
    // 回到主线程刷新
    DispatchQueue.main.async {
        self.collectionView.reloadData()
    }
}

4. 优化自定义布局的prepare()方法

针对你用的GreedoSizeCalculator,可以把尺寸计算逻辑移到自定义布局的prepare()方法里,而不是每次请求item尺寸时才计算。在prepare()中异步预计算所有item的尺寸,存到布局缓存中,这样布局系统请求尺寸时直接返回缓存值即可。

示例代码(自定义布局中):

override func prepare() {
    super.prepare()
    guard let dataSource = self.dataSource as? GreedoSizeCalculatorDataSource else { return }
    
    DispatchQueue.global(qos: .userInitiated).async {
        // 预计算所有item的尺寸
        var itemSizes: [IndexPath: CGSize] = [:]
        for section in 0..<self.collectionView?.numberOfSections ?? 0 {
            for item in 0..<self.collectionView?.numberOfItems(inSection: section) ?? 0 {
                let indexPath = IndexPath(item: item, section: section)
                let asset = dataSource.greedoSizeCalculator(self, originalImageSizeAt: indexPath)
                let aspectRatio = CGFloat(asset.pixelWidth) / CGFloat(asset.pixelHeight)
                let targetWidth = UIScreen.main.bounds.width / 2
                itemSizes[indexPath] = CGSize(width: targetWidth, height: targetWidth / aspectRatio)
            }
        }
        
        // 主线程更新布局缓存并刷新
        DispatchQueue.main.async {
            self.itemSizeCache = itemSizes
            self.invalidateLayout()
        }
    }
}

5. 懒加载+限制初始加载数量

如果相册里照片特别多,初期不要一次性加载所有asset。可以先加载前20-30张,计算好尺寸显示出来,剩下的等用户滚动到接近底部时再加载计算,这样初期的计算量会大幅降低,不会出现明显卡顿。

6. 图片加载优化

最后,图片本身的加载也要异步处理,用成熟的图片加载库(比如SDWebImage、Kingfisher)来加载缩略图,不要直接加载原图。这样不仅能减少内存占用,还能避免因为图片解码导致的主线程卡顿。

内容的提问来源于stack exchange,提问作者TheRedCamaro3.0 3.0

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:07:31