使用 CloudKit + CoreData,如何在没有 SwiftUI 的 @FetchRequest 的情况下从远程云更新中更新 UI?
Posted
技术标签:
【中文标题】使用 CloudKit + CoreData,如何在没有 SwiftUI 的 @FetchRequest 的情况下从远程云更新中更新 UI?【英文标题】:Using CloudKit + CoreData, how to update UI from remote cloud update without SwiftUI's @FetchRequest? 【发布时间】:2021-02-06 10:14:01 【问题描述】:大部分 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 实现自动执行它一样。该方法被许多不同的线程非常频繁地调用(仅在启动时多次)。
【问题讨论】:
您在这方面取得了进展吗? @FetchRequest 似乎是反 MVVM,但我遇到了你提到的同样的问题。 【参考方案1】:这是一个完整的工作示例,当 CloudKit
中的某些内容发生变化时,它会使用 CoreData
+ CloudKit
+ MVVM
更新 UI。与通知相关的代码用 cmets 标记,请参阅 CoreDataManager
和 SwiftUI
文件。不要忘记在 Xcode 中添加适当的 Capabilities,请参见下图。
持久性/数据管理器
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 设置
感谢来自this 线程的Didier B.
。
【讨论】:
以上是关于使用 CloudKit + CoreData,如何在没有 SwiftUI 的 @FetchRequest 的情况下从远程云更新中更新 UI?的主要内容,如果未能解决你的问题,请参考以下文章
App 如何根据需要动态为 CoreData 开启 CloudKit 云同步功能
CoreData+CloudKit iOS13 NSPersistentStoreRemoteChangeNotification
如何更灵活的处理 CloudKit 从云端同步到本地的 CoreData 托管对象
如何更灵活的处理 CloudKit 从云端同步到本地的 CoreData 托管对象