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

解决从CoreData加载图片列表时SwiftUI卡顿问题

SwiftUI LazyHGrid 加载大量图片卡顿的优化方案

你的问题核心在于:虽然LazyHGrid是懒加载视图,但当前代码会提前取出所有图片的原始数据并同步解码,导致150张1080P图片在首次进入页面时全部被处理,直接阻塞主线程造成卡顿。以下是针对性的优化方案:

1. 预生成并存储缩略图(最优解)

直接加载1080P原图完全没必要——你的Grid高度只有180px,只需要对应尺寸的缩略图就能满足显示需求。

步骤:

  • 在Core Data的Photo实体中新增thumbnailData属性(Binary Data类型)
  • 保存图片时,生成对应Grid尺寸的缩略图并存储:
// 生成指定尺寸的缩略图
func generateThumbnail(from imageData: Data, targetHeight: CGFloat) -> Data? {
    guard let originalImage = UIImage(data: imageData) else { return nil }
    // 按16:9比例计算宽度(适配1080P原图比例)
    let targetWidth = targetHeight * 16 / 9
    let targetSize = CGSize(width: targetWidth, height: targetHeight)
    
    let renderer = UIGraphicsImageRenderer(size: targetSize)
    let thumbnail = renderer.image { context in
        originalImage.draw(in: CGRect(origin: .zero, size: targetSize))
    }
    return thumbnail.jpegData(compressionQuality: 0.8)
}

// 保存Photo时调用
let thumbnailData = generateThumbnail(from: originalImageData, targetHeight: 180)
let newPhoto = Photo(context: viewContext)
newPhoto.filteredData = originalImageData
newPhoto.thumbnailData = thumbnailData
  • 在PhotosView中替换为加载缩略图:
ForEach(photos) { photo in
    if let data = photo.thumbnailData {
        Image(data: data)
            .resizable()
            .scaledToFit()
    }
}

2. 异步解码图片,避免阻塞主线程

如果暂时无法修改Core Data模型,可以通过异步解码将图片处理逻辑移到后台线程:

自定义异步图片视图:

struct AsyncLocalImage: View {
    let imageData: Data
    let targetHeight: CGFloat
    
    @State private var loadedImage: UIImage?
    
    var body: some View {
        // 占位图(可自定义样式)
        Color.gray
            .frame(height: targetHeight)
            .overlay {
                if let image = loadedImage {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                }
            }
            .task {
                await loadAndScaleImage()
            }
    }
    
    private func loadAndScaleImage() async {
        // 后台线程处理图片
        let processedImage = await DispatchQueue.global(qos: .userInitiated).sync {
            guard let originalImage = UIImage(data: imageData) else { return nil }
            let targetWidth = targetHeight * 16 / 9
            return originalImage.scale(to: CGSize(width: targetWidth, height: targetHeight))
        }
        // 主线程更新UI
        await MainActor.run {
            loadedImage = processedImage
        }
    }
}

// UIImage扩展:缩放图片
extension UIImage {
    func scale(to size: CGSize) -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { _ in
            draw(in: CGRect(origin: .zero, size: size))
        }
    }
}

在PhotosView中使用:

ForEach(photos) { photo in
    if let data = photo.filteredData {
        AsyncLocalImage(imageData: data, targetHeight: gridHeight)
    }
}

3. 延迟加载图片数据

通过标记已进入可见区域的图片,避免提前取出所有图片数据:

struct PhotosView : View {
    @FetchRequest(sortDescriptors: [SortDescriptor(\Photo.date, order: .reverse)], animation: .default)
    private var photos: FetchedResults<Photo>

    @State private var gridHeight = 180.0
    @State private var loadedPhotoIDs: Set<UUID> = [] // 假设Photo有UUID类型的id属性

    var body: some View {
        VStack {
            ScrollView(.horizontal) {
                LazyHGrid(rows: [GridItem(.fixed(gridHeight))], alignment: .top, spacing: 0) {
                    ForEach(photos) { photo in
                        if loadedPhotoIDs.contains(photo.id) {
                            if let data = photo.filteredData {
                                Image(data: data)
                                    .resizable()
                                    .scaledToFit()
                            }
                        } else {
                            Color.gray
                                .frame(height: gridHeight)
                                .onAppear {
                                    loadedPhotoIDs.insert(photo.id)
                                }
                        }
                    }
                }
                .frame(height: 200)
            }
        }
    }
}

为什么原代码会卡顿?

LazyHGrid的懒加载是指视图的创建和渲染是懒加载的,但你的代码中if let data = photo.filteredData会在SwiftUI构建视图树时被立即执行——遍历所有150条Photo数据并取出图片Data,紧接着Image(data: data)会在主线程同步解码整张1080P图片,导致CPU瞬间被占满,引发卡顿。

内容的提问来源于stack exchange,提问作者meaning-matters

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:17:06