SwiftUI中如何在List视图中懒加载CoreData对象?
解决CoreData分类列表卡顿与LazyVStack显示问题
一、解决列表卡顿:实现数据+图片的懒加载
1. CoreData层面:改用NSFetchedResultsController实现数据懒加载
原代码一次性将所有Category数据拉取到内存,数据量大时必然引发卡顿。改用NSFetchedResultsController后,它会自动管理数据的按需加载,配合SwiftUI的List(本身是懒加载容器),只有当行即将进入可视区域时才会获取对应实体。
修改CategoryServiceModel:
class CategoryServiceModel: ObservableObject { let manager: CoreDataManager private var fetchedResultsController: NSFetchedResultsController<Category>? @Published var categories: [Category] = [] init(coreDataManager: CoreDataManager = .instance) { self.manager = coreDataManager setupFetchedResultsController() loadCategories() } private func setupFetchedResultsController() { let request = NSFetchRequest<Category>(entityName: "Category") let sort = NSSortDescriptor(keyPath: \Category.name, ascending: true) request.sortDescriptors = [sort] fetchedResultsController = NSFetchedResultsController( fetchRequest: request, managedObjectContext: manager.context, sectionNameKeyPath: nil, cacheName: nil ) fetchedResultsController?.delegate = self } func loadCategories() { do { try fetchedResultsController?.performFetch() categories = fetchedResultsController?.fetchedObjects ?? [] removeDuplicateCategoriesIfNeeded() } catch let error { print("Error fetching categories. \(error.localizedDescription)") } } } extension CategoryServiceModel: NSFetchedResultsControllerDelegate { func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { guard let updatedCategories = controller.fetchedObjects as? [Category] else { return } categories = updatedCategories } }
2. 图片懒加载:仅在视图显示时异步加载图片
原代码在ForEach中同步转换所有Category的图片数据,哪怕未显示的行也会处理,这是卡顿的核心诱因。我们将图片逻辑抽离为子视图,仅在视图出现时异步加载:
struct CategoryRowView: View { let category: Category let imageSize: CGFloat = 35 @State private var uiImage: UIImage? var body: some View { HStack { if let image = uiImage { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) .frame(width: imageSize, height: imageSize) .padding(10) .clipShape(Circle()) .overlay(Circle().stroke(.gray, lineWidth: 1)) } else { Circle() .frame(width: imageSize, height: imageSize) .foregroundColor(.gray.opacity(0.2)) } Text(category.name ?? "") } .onAppear { Task { if let imageData = category.image { let image = UIImage(data: imageData) await MainActor.run { uiImage = image } } } } } }
修改CategoriesView的列表部分:
struct CategoriesView: View { @EnvironmentObject private var categorySM: CategoryServiceModel var body: some View { NavigationStack { List { ForEach(categorySM.categories, id: \.self) { category in CategoryRowView(category: category) } } } } }
二、解决LazyVStack无法显示的问题
LazyVStack本身不具备滚动能力,必须包裹在ScrollView中才能正常显示和滚动,同时要模拟List的布局样式:
struct CategoriesView: View { @EnvironmentObject private var categorySM: CategoryServiceModel var body: some View { NavigationStack { ScrollView { LazyVStack(spacing: 0) { ForEach(categorySM.categories, id: \.self) { category in CategoryRowView(category: category) .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical, 8) .padding(.horizontal, 16) } } } .navigationTitle("Categories") } } }
额外优化建议
- 对CoreData的image字段,优先存储缩略图而非原图,减少单次加载的数据量;
- 若图片处理逻辑复杂,可添加内存缓存,避免重复解析图片数据;
- 确保
removeDuplicateCategoriesIfNeeded方法逻辑高效,避免在主线程执行大量计算。
内容的提问来源于stack exchange,提问作者fs_tigre
相关产品推荐
相关产品推荐

