如何为加载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
相关产品推荐
相关产品推荐

