解决从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
相关产品推荐
相关产品推荐

