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

如何异步构建SwiftUI视图以解决照片加载UI卡顿问题

解决SwiftUI中异步构建堆叠照片视图的UI卡顿问题

你的卡顿问题核心是在主线程同步加载PHAsset缩略图——每次构建myImageStack时,都会同步调用getAssetThumbnail(),这会阻塞UI线程,尤其是照片数量多的时候。下面是具体的解决方案,既保留堆叠效果,又实现异步加载避免冻结:

核心思路

  1. 将缩略图加载逻辑移到后台线程,完成后再更新UI
  2. 用状态对象缓存已加载的缩略图,避免重复请求
  3. 主视图异步初始化照片数据,先显示加载状态再展示内容

修改后的代码

1. 自定义缩略图加载器(管理异步加载与缓存)

import SwiftUI
import Photos

class PhotoThumbnailLoader: ObservableObject {
    @Published var thumbnails: [String: UIImage] = [:]
    
    func loadThumbnail(for asset: PHAsset) {
        // 避免重复加载已缓存的缩略图
        guard thumbnails[asset.localIdentifier] == nil else { return }
        
        Task {
            let options = PHImageRequestOptions()
            options.deliveryMode = .highQualityFormat
            options.isSynchronous = false
            options.resizeMode = .fast
            
            PHImageManager.default().requestImage(
                for: asset,
                targetSize: CGSize(width: 200, height: 200),
                contentMode: .aspectFit,
                options: options
            ) { [weak self] image, _ in
                guard let image = image, let self = self else { return }
                DispatchQueue.main.async {
                    self.thumbnails[asset.localIdentifier] = image
                }
            }
        }
    }
}

2. 重构图片栈视图(异步加载+占位符)

struct MyImageStack: View {
    let resultsPhotos: PHFetchResult<PHAsset>
    @StateObject private var loader = PhotoThumbnailLoader()
    
    var body: some View {
        let collection = PHFetchResultCollection(fetchResult: resultsPhotos)
        
        ZStack {
            ForEach(collection, id: \.localIdentifier) { asset in
                if let thumbnail = loader.thumbnails[asset.localIdentifier] {
                    Image(uiImage: thumbnail)
                        .resizable()
                        .scaledToFit()
                        .border(Color.white, width: 10)
                        .shadow(color: Color.black.opacity(0.2), radius: 5, x: 5, y: 5)
                        .frame(width: 200, height: 200)
                        .rotationEffect(.degrees(Double.random(in: -25...25)))
                } else {
                    // 占位符:灰色背景,加载时显示
                    RoundedRectangle(cornerRadius: 8)
                        .fill(Color.gray.opacity(0.3))
                        .frame(width: 200, height: 200)
                        .onAppear {
                            loader.loadThumbnail(for: asset)
                        }
                }
            }
        }
    }
}

3. 主视图异步初始化照片数据

struct PhotosOverviewView: View {
    @State private var resultPhotos: [String: PHFetchResult<PHAsset>] = [:]
    @State private var isLoading = true
    
    var body: some View {
        NavigationStack {
            if isLoading {
                ProgressView("加载照片中...")
                    .onAppear {
                        loadPhotosAsync()
                    }
            } else {
                ScrollView(.vertical) {
                    VStack(alignment: .center) {
                        ForEach(Array(resultPhotos.keys).sorted(), id: \.self) { key in
                            NavigationLink(destination: PhotosYearView(year: key, resultsPhotos: resultPhotos[key]!)) {
                                MyImageStack(resultsPhotos: resultPhotos[key]!)
                                    .padding()
                            }
                        }
                    }
                    .frame(maxWidth: .infinity)
                }
            }
        }
    }
    
    private func loadPhotosAsync() {
        Task {
            let photosModel = PhotosModel()
            // 异步请求权限
            await PHPhotoLibrary.requestAuthorization(for: .readWrite)
            // 获取目标日期的照片
            let fetchedPhotos = photosModel.getAllPhotosOnDay(monthAbbr: "Feb", day: 16)
            
            // 回到主线程更新状态
            DispatchQueue.main.async {
                self.resultPhotos = fetchedPhotos
                self.isLoading = false
            }
        }
    }
}

关键优化点

  • 异步加载隔离:缩略图请求放在后台线程,不会阻塞主线程,UI始终保持响应
  • 缓存机制:已加载的缩略图存在thumbnails字典里,滚动时不会重复请求
  • 加载状态反馈:主视图先显示进度条,避免用户看到空白或冻结的界面
  • 保留堆叠效果:每个图片加载完成后自动替换占位符,随机旋转的视觉效果完全保留

可选优化方向

如果照片数量特别多,可以进一步优化:

  • 限制同时加载的缩略图数量(比如用信号量控制并发数)
  • 对ZStack底层的图片延迟加载(比如只加载前N层,滚动到视图时再加载底层)
  • 降低缩略图的目标尺寸(如果视觉效果允许的话)

内容的提问来源于stack exchange,提问作者Martin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 05:47:03