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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 16:47:03