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

如何优化用户本地相册照片加载至UICollectionView的速度

解决UICollectionView加载本地相册照片耗时过长的问题

你遇到的核心问题是在主线程同步请求相册图片,这直接阻塞了UICollectionView的渲染流程,导致启动和滚动时严重卡顿。咱们一步步拆解问题并给出优化方案:

核心问题分析

  1. 同步请求阻塞主线程:你设置了requestOptions.isSynchronous = true,而cellForItemAt本身在主线程执行,同步请求会让主线程等待图片加载完成才继续,直接卡住UI。
  2. 重复创建资源:每次生成cell都重新创建PHImageRequestOptions,造成不必要的资源浪费。
  3. 图片逻辑错误:在闭包里重复向imageArray添加图片,cell复用后会导致数组无限增大,既浪费内存又毫无意义。
  4. 请求尺寸不合理:用未完成布局的cell.bounds计算图片尺寸,会导致请求的图片大小不符合实际显示需求,增加加载时间。
  5. 未处理复用冲突: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:28:29