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

SwiftUI中如何从Core Data获取展示数据?解决越界及存储问题

问题分析与解决方案

核心问题拆解

  1. 数据仅滚动到底部才保存:LazyVGrid的onAppear仅在单元格显示到屏幕时触发,未滚动到的单元格不会执行保存逻辑,导致数据不全。
  2. 数组越界错误:大概率是通过索引匹配API数据与Core Data数据,但两者的顺序/数量不一致引发的。
  3. Core Data数据展示位置不明:未通过唯一标识关联API数据与Core Data数据,无法在SearchResultRow中精准获取对应存储内容。

解决方案与代码修改

1. 优化Core Data操作逻辑

先给Item实体添加唯一标识字段id(类型选Integer 64),对应API返回作品的id,避免重复保存。然后扩展PersistenceController,添加批量保存和按id查询的方法:

extension PersistenceController {
    // 批量保存API返回的所有数据
    func saveAllResults(_ results: [ResultItem]) {
        for result in results {
            // 先检查数据是否已存在,避免重复存储
            let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
            fetchRequest.predicate = NSPredicate(format: "id == %d", result.id)
            
            if let existingItem = try? container.viewContext.fetch(fetchRequest).first {
                existingItem.title = result.title // 已存在则更新标题
            } else {
                let newItem = Item(context: container.viewContext)
                newItem.id = Int64(result.id)
                newItem.title = result.title
            }
        }
        
        do {
            try container.viewContext.save()
        } catch {
            print("Core Data保存失败:\(error)")
        }
    }
    
    // 根据作品id查询Core Data中的对应数据
    func getItemById(_ id: Int) -> Item? {
        let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "id == %d", id)
        return try? container.viewContext.fetch(fetchRequest).first
    }
}

2. 修改ViewModel,API返回后批量保存数据

把保存逻辑从单元格移到API请求完成后,一次性保存所有数据:

class HomeViewModel: ObservableObject {
    @Published var results = [ResultItem]()
    private let coreDM: PersistenceController
    
    // 初始化时传入Core Data单例,避免重复创建实例
    init(coreDM: PersistenceController = .shared) {
        self.coreDM = coreDM
    }
    
    func performSearch() {
        guard let gUrl = URL(string: "https://api.artic.edu/api/v1/artworks") else { return }
        
        Task {
            do {
                let (data, _) = try await URLSession.shared.data(from: gUrl)
                let response = try JSONDecoder().decode(ResponseData.self, from: data)
                
                DispatchQueue.main.async { [weak self] in
                    guard let self = self, let results = response.data else { return }
                    self.results = results
                    // API数据返回后直接批量保存到Core Data
                    self.coreDM.saveAllResults(results)
                }
            } catch {
                print("API请求失败:\(error)")
            }
        }
    }
}

3. 调整ContentView,传递必要参数

不再在SearchResultRow中创建新的PersistenceController,改用单例,并传递作品id用于查询:

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.title, ascending: true)],
        animation: .default)
    private var items: FetchedResults<Item>
    
    @StateObject private var viewModel: HomeViewModel
    
    init() {
        _viewModel = StateObject(wrappedValue: HomeViewModel(coreDM: .shared))
    }
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: Array(repeating: .init(.flexible()), count: UIDevice.current.userInterfaceIdiom == .pad ? 4 : 2)) {
                    ForEach(viewModel.results, id: \.id) { result in
                        let resultVM = ResultVM(model: result)
                        NavigationLink(destination: DetailView(data: resultVM.trackName)) {
                            SearchResultRow(resultVM: resultVM, coreDM: .shared, itemId: result.id)
                        }
                    }
                }
            }
        }
        .onAppear {
            viewModel.performSearch()
        }
    }
}

4. 修改SearchResultRow,展示Core Data数据

根据传入的作品id查询Core Data,优先展示存储的数据,没有则用API数据:

struct SearchResultRow: View {
    let resultVM: ResultVM
    let coreDM: PersistenceController
    let itemId: Int
    
    // 实时查询对应Core Data数据
    private var coreDataItem: Item? {
        coreDM.getItemById(itemId)
    }
    
    var body: some View {
        HStack {
            RoundedRectangle(cornerRadius: 16)
                .fill(.yellow)
                .frame(maxWidth: .infinity)
                .aspectRatio(1, contentMode: .fit)
                // 优先显示Core Data的数据, fallback到API数据
                .overlay(Text(coreDataItem?.title ?? resultVM.trackName))
        }
        .padding()
        .background(Color.red)
    }
}

关键注意事项

  • 始终使用PersistenceController.shared单例,避免创建多个上下文导致数据不一致
  • 用唯一id匹配API与Core Data数据,彻底解决数组越界问题
  • 批量保存逻辑确保所有API数据一次性存入Core Data,无需依赖滚动触发

内容的提问来源于stack exchange,提问作者New iOS Dev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 16:15:38