如何优化用户本地相册照片加载至UICollectionView的速度
解决UICollectionView加载本地相册照片耗时过长的问题
你遇到的核心问题是在主线程同步请求相册图片,这直接阻塞了UICollectionView的渲染流程,导致启动和滚动时严重卡顿。咱们一步步拆解问题并给出优化方案:
核心问题分析
- 同步请求阻塞主线程:你设置了
requestOptions.isSynchronous = true,而cellForItemAt本身在主线程执行,同步请求会让主线程等待图片加载完成才继续,直接卡住UI。 - 重复创建资源:每次生成cell都重新创建
PHImageRequestOptions,造成不必要的资源浪费。 - 图片逻辑错误:在闭包里重复向
imageArray添加图片,cell复用后会导致数组无限增大,既浪费内存又毫无意义。 - 请求尺寸不合理:用未完成布局的cell.bounds计算图片尺寸,会导致请求的图片大小不符合实际显示需求,增加加载时间。
- 未处理复用冲突:cell复用时,旧的图片请求如果未完成,返回后会覆盖新图片,造成图片错乱。
优化方案与代码实现
第一步:重构ViewController基础代码
import UIKit import Photos class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource { var collectionView: UICollectionView! var fetchResult: PHFetchResult<PHAsset> = PHFetchResult() // 复用请求配置,避免重复创建 private let imageRequestOptions: PHImageRequestOptions = { let options = PHImageRequestOptions() options.deliveryMode = .fastFormat // 优先加载低清图提升感知速度,可按需改为.highQualityFormat options.isNetworkAccessAllowed = true // 允许加载iCloud照片 return options }() // 存储请求ID,用于取消复用cell的旧请求 private var imageRequestIDs: [IndexPath: PHImageRequestID] = [:] override func viewDidLoad() { super.viewDidLoad() setupCollectionView() checkPhotoPermission() } private func setupCollectionView() { let layout = UICollectionViewFlowLayout() layout.minimumInteritemSpacing = 5.0 layout.minimumLineSpacing = 5.0 layout.sectionInset = UIEdgeInsets(top: 10.0, left: 5.0, bottom: 5.0, right: 5.0) collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.delegate = self collectionView.dataSource = self collectionView.register(CustomPhotoCell.self, forCellWithReuseIdentifier: "Cell") collectionView.backgroundColor = .white view.addSubview(collectionView) // 简化布局约束写法 NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: view.topAnchor), collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) } private func checkPhotoPermission() { let status = PHPhotoLibrary.authorizationStatus() switch status { case .authorized: fetchAssets() collectionView.reloadData() case .notDetermined: PHPhotoLibrary.requestAuthorization { [weak self] newStatus in guard let self = self else { return } DispatchQueue.main.async { if newStatus == .authorized { self.fetchAssets() self.collectionView.reloadData() } else { print("照片权限未授予") } } } default: print("照片权限未授予") } } private func fetchAssets() { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions) } }
第二步:优化cellForItemAt与布局逻辑
extension ViewController { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return fetchResult.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CustomPhotoCell // 取消当前cell的旧请求,避免图片错乱 if let oldRequestID = imageRequestIDs[indexPath] { PHImageManager.default().cancelImageRequest(oldRequestID) imageRequestIDs.removeValue(forKey: indexPath) } // 计算合理的图片请求尺寸(以3列布局为例) let columnCount: CGFloat = 3 let spacing = (collectionView.collectionViewLayout as! UICollectionViewFlowLayout).minimumInteritemSpacing let availableWidth = view.bounds.width - (collectionView.collectionViewLayout as! UICollectionViewFlowLayout).sectionInset.left - (collectionView.collectionViewLayout as! UICollectionViewFlowLayout).sectionInset.right - (spacing * (columnCount - 1)) let itemWidth = availableWidth / columnCount let scale = UIScreen.main.scale let targetSize = CGSize(width: itemWidth * scale, height: itemWidth * scale) // 异步请求图片,不阻塞主线程 let asset = fetchResult.object(at: indexPath.item) let requestID = PHImageManager.default().requestImage( for: asset, targetSize: targetSize, contentMode: .aspectFill, options: imageRequestOptions ) { [weak cell, weak self] image, _ in guard let self = self, let cell = cell else { return } // 确保cell仍对应当前indexPath,避免复用后错误更新 if let currentIndexPath = collectionView.indexPath(for: cell), currentIndexPath == indexPath { cell.imgView.image = image cell.imgView.contentMode = .scaleAspectFill cell.imgView.clipsToBounds = true // 配合cell圆角,防止图片溢出 } self.imageRequestIDs.removeValue(forKey: indexPath) } imageRequestIDs[indexPath] = requestID cell.layer.cornerRadius = 10 cell.layer.masksToBounds = true cell.backgroundColor = .white return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let columnCount: CGFloat = 3 let spacing = (collectionViewLayout as! UICollectionViewFlowLayout).minimumInteritemSpacing let availableWidth = view.bounds.width - (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset.left - (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset.right - (spacing * (columnCount - 1)) let itemWidth = availableWidth / columnCount return CGSize(width: itemWidth, height: itemWidth) // 正方形cell,可按需调整为瀑布流尺寸 } } // 规范自定义cell类名与布局 class CustomPhotoCell: UICollectionViewCell { let imgView: UIImageView = { let iv = UIImageView() iv.translatesAutoresizingMaskIntoConstraints = false iv.contentMode = .scaleAspectFill iv.clipsToBounds = true return iv }() override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(imgView) NSLayoutConstraint.activate([ imgView.topAnchor.constraint(equalTo: contentView.topAnchor), imgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), imgView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), imgView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
额外优化建议
- 如果需要更流畅的滚动体验,可以使用
PHCachingImageManager预加载即将显示的照片。 - 若要显示高清图,可将
deliveryMode改为.opportunistic,实现先显示低清占位图,再自动替换为高清图。 - 避免在
cellForItemAt中执行任何耗时操作,所有数据准备工作都应提前在后台完成。
内容的提问来源于stack exchange,提问作者TheRedCamaro3.0 3.0
相关产品推荐
相关产品推荐

