1

大多数 CloudKit+CoreData 教程使用 SwiftUI,它们的实现包括 @FetchRequest,它会自动检测 CoreData 获取中的变化并刷新 UI。

https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-fetchrequest-property-wrapper

如果没有 SwiftUI,我将如何实现这一目标?我希望能够控制刷新 UI 的方式,以响应检测到由于 iCloud 更新而发生的 CoreData 更改。

我有这个来设置 NSPersistentCloudKitContainer 并注册远程通知:

let storeDescription = NSPersistentStoreDescription()
    storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

    let container = NSPersistentCloudKitContainer(name: "CoreDataDemo")
    container.persistentStoreDescriptions = [storeDescription]
    
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    
    container.viewContext.automaticallyMergesChangesFromParent = true
    container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    
    NotificationCenter.default.addObserver(self, selector: #selector(didReceiveCloudUpdate), name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)

但是我不知道如何处理 .NSPersistentStoreRemoteChange 与 SwiftUI 实现自动执行的方式相同。该方法从许多不同的线程中被非常频繁地调用(仅在启动时多次)。

4

1 回答 1

2

这是一个完整的工作示例,当CloudKit使用CoreData++发生变化时更新CloudKitUI MVVM。与通知相关的代码标有注释、查看 CoreDataManagerSwiftUI文件。不要忘记在 Xcode 中添加适当的功能,请参见下图。

持久性/数据管理器

import CoreData
import SwiftUI

class CoreDataManager{
    
    static let instance = CoreDataManager()
    let container: NSPersistentCloudKitContainer
    
    let context: NSManagedObjectContext

    init(){
        container = NSPersistentCloudKitContainer(name: "CoreDataContainer")
        
       
        guard let description = container.persistentStoreDescriptions.first else{
            fatalError("###\(#function): Failed to retrieve a persistent store description.")
        }
        
        description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)

        // Generate NOTIFICATIONS on remote changes
        description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

        container.loadPersistentStores { (description, error) in
            if let error = error{
                print("Error loading Core Data. \(error)")
            }
        }
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

        context = container.viewContext
    }
    
    func save(){
        do{
            try context.save()
            print("Saved successfully!")
        }catch let error{
            print("Error saving Core Data. \(error.localizedDescription)")
        }
    }
}

查看模型

import CoreData

class CarViewModel: ObservableObject{
    let manager = CoreDataManager.instance
    @Published var cars: [Car] = []
    
    init(){
        getCars()
    }

    func addCar(model:String, make:String?){
        let car = Car(context: manager.context)
        car.make = make
        car.model = model

        save()
        getCars()
    }
    
    func getCars(){
        let request = NSFetchRequest<Car>(entityName: "Car")
        
        let sort = NSSortDescriptor(keyPath: \Car.model, ascending: true)
        request.sortDescriptors = [sort]

        do{
            cars =  try manager.context.fetch(request)
        }catch let error{
            print("Error fetching cars. \(error.localizedDescription)")
        }
    }
    
    func deleteCar(car: Car){
        manager.context.delete(car)
        save()
        getCars()
    }

    func save(){
        self.manager.save()
    }
}

SwiftUI

import SwiftUI
import CoreData

struct ContentView: View {
    @StateObject var carViewModel = CarViewModel()
    
    @State private var makeInput:String = ""
    @State private var modelInput:String = ""
    
    // Capture NOTIFICATION changes
    var didRemoteChange = NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange).receive(on: RunLoop.main)

    @State private var deleteCar: Car?
    
    var body: some View {
        NavigationView {
            VStack{
                List {
                    if carViewModel.cars.isEmpty {
                        Text("No cars")
                            .foregroundColor(.gray)
                            .fontWeight(.light)
                    }
                    ForEach(carViewModel.cars) { car in
                        HStack{
                            Text(car.model ?? "Model")
                            Text(car.make ?? "Make")
                                .foregroundColor(Color(UIColor.systemGray2))
                        }
                        .swipeActions{
                            Button( role: .destructive){
                                carViewModel.deleteCar(car: car)
                            }label:{
                                Label("Delete", systemImage: "trash.fill")
                            }
                        }
                    }

                }
                // Do something on NOTIFICATION
                .onReceive(self.didRemoteChange){ _ in
                    carViewModel.getCars()
                }

                Spacer()
                Form {
                    TextField("Make", text:$makeInput)
                    TextField("Model", text:$modelInput)
                }
                .frame( height: 200)
                
                Button{
                    saveNewCar()
                    makeInput = ""
                    modelInput = ""
                }label: {
                    Image(systemName: "car")
                    Text("Add Car")
                }
                .padding(.bottom)
            }
        }
    }
    
    func saveNewCar(){
        if !modelInput.isEmpty{
            carViewModel.addCar(model: modelInput, make: makeInput.isEmpty ? nil : makeInput)
        }
    }
}

核心数据容器

实体

Car

属性

制作String
模型String

Xcode/CloudKit 设置

在此处输入图像描述

感谢Didier B.来自这个线程。

于 2021-12-30T14:35:33.273 回答