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

如何为加载CoreData图片的UICollectionView添加加载遮罩并优化逻辑

实现方案及代码优化

现有代码的核心问题

  • 未处理cell复用逻辑:异步转换图片完成时,cell可能已经被滑动复用,会出现图片错配的问题
  • 无图片缓存:每次cell滑入可视区域都会重复执行Data转UIImage的逻辑,重复消耗性能,是加载慢的核心原因
  • 无统一的加载完成回调,无法判断所有图片何时全部处理完成

优化方案

第一步:ViewModel层添加缓存与加载状态统计

在你的ViewModel中添加以下属性,用于缓存已处理的图片、统计加载进度:

// 已处理好的图片缓存,建议用数据id做key更稳妥,这里用index暂时演示
var imageCache: [Int: UIImage] = [:]
// 全部需要处理的图片总数
private var totalNeedLoadImageCount: Int { locations.filter{ $0.image != nil }.count }
// 已处理完成的图片计数
private var loadedImageCount = 0
// 全部图片加载完成的回调
var allImagesLoadCompletion: (() -> Void)?

第二步:优化cell图片加载逻辑,处理复用问题

修改cellForItemAt代码如下:

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.reuseIdentifier, for: indexPath) as? LocationCollectionViewCell else {
        return collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.reuseIdentifier, for: indexPath)
    }
    let location = viewModel.locations[indexPath.row]
    cell.name.text = location.name
    cell.unlocked.text = location.unlocked ? "Unlocked" : "Locked"
    // 每次cell复用前重置图片,防止显示旧图
    cell.image.image = nil
    // 显示cell级别的加载指示器(可选,也可以仅用全局遮罩)
    cell.loadingIndicator.startAnimating()

    if let data = location.image {
        // 先查缓存,有缓存直接用
        if let cachedImage = viewModel.imageCache[indexPath.row] {
            cell.image.image = location.unlocked ? cachedImage : cachedImage.grayscale()
            cell.loadingIndicator.stopAnimating()
        } else {
            // 异步处理图片
            DispatchQueue.global(qos: .userInitiated).async { [weak self] in
                guard let self = self else { return }
                let image = UIImage(data: data)
                // 处理灰度逻辑
                let finalImage = location.unlocked ? image : image?.grayscale()
                // 存入缓存
                if let finalImage = finalImage {
                    self.viewModel.imageCache[indexPath.row] = finalImage
                }
                DispatchQueue.main.async {
                    // 校验当前cell是否还对应原indexPath,防止复用错配
                    guard let currentIndexPath = collectionView.indexPath(for: cell), currentIndexPath == indexPath else {
                        return
                    }
                    cell.image.image = finalImage
                    cell.loadingIndicator.stopAnimating()
                    // 计数+1,判断是否全部加载完成
                    self.viewModel.loadedImageCount += 1
                    if self.viewModel.loadedImageCount == self.viewModel.totalNeedLoadImageCount {
                        self.viewModel.allImagesLoadCompletion?()
                    }
                }
            }
        }
    } else {
        let placeholder = UIImage(named: "noun_Akropolis_403786")
        cell.image.image = location.unlocked ? placeholder : placeholder?.grayscale()
        cell.loadingIndicator.stopAnimating()
    }
    return cell
}

第三步:添加全局加载遮罩

在ViewController的viewDidLoad中添加全局遮罩,监听加载完成回调:

// 声明全局加载指示器属性
private var loadingView: UIActivityIndicatorView?

override func viewDidLoad() {
    super.viewDidLoad()
    // 添加全局加载遮罩,可自定义样式
    let loadingView = UIActivityIndicatorView(style: .large)
    loadingView.center = view.center
    view.addSubview(loadingView)
    self.loadingView = loadingView
    loadingView.startAnimating()
    
    // 监听全部图片加载完成回调
    viewModel.allImagesLoadCompletion = { [weak self] in
        self?.loadingView?.stopAnimating()
        self?.loadingView?.isHidden = true
    }
}

更优性能方案推荐

如果图片体积普遍偏大,建议不要直接把图片Data存在CoreData中:

  • 把图片写入沙盒目录,CoreData只存储图片的本地路径,读取时直接从沙盒加载,性能会比从CoreData读取二进制数据再转UIImage高30%以上
  • 可以提前预加载当前屏幕+前后一屏的图片,不用等cell进入可视区域才开始处理,进一步降低用户感知的加载延迟

内容的提问来源于stack exchange,提问作者Peter Hlavatík

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 04:57:03