[英]How to read arrays with NSPersistentCloudKitContainer (CloudKit with Core Data)?
[英]How to fetch CloudKit data manually to update the UI using NSPersistentCloudKitContainer and SwiftUI?
假设我们有一个有效的 NSPersistentCloudKitContainer 和一个名为 Item 的 CoreData 实体。 假设我们想要在 iOS、iPad 和 Mac 应用程序之间同步。
我观看了有关同步 CoreData 和 CloudKit 的 WWDC 会议,并在我的 SwiftUI 应用程序中实现了基本同步。 它可以工作,数据会发送到所有 3 台设备并自动同步。
但是在 Mac 上,如果不重新加载视图(以及在模拟器中(因为它们没有收到推送通知),数据不会出现。它来了,但没有自动刷新。我还测试了模拟器到真实设备,有时它会自动同步,有时我需要重新打开应用程序才能看到更改。
我很好奇是否有一种可以与 NSPersistentCloudKitContainer 一起使用的强制获取方法。 或者也许有人知道在使用 NSPersistentCloudKitContainer 和 SwiftUI 时手动重新获取数据的解决方法?
我还想在新数据开始出现时显示一个活动指示器,但不确定在代码中从哪里开始获取获取点?
我使用了 Apple 提供的这个 UIKit 示例代码,并将其调整为在 SwiftUI 中工作。 我还阅读了此处的所有文档。
这是我使用的persistentContainer代码:
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "AppName")
container.persistentStoreDescriptions.first?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
如果你看到,我没有在上面的代码中包含container.viewContext.transactionAuthor = appTransactionAuthorName
和setQueryGenerationFrom(.current)
和NotificationCenter.default.addObserver
。 但我也尝试过使用它们并得到相同的结果。 不确定我是否需要使用它们,因为同步也可以在没有这些调用的情况下进行。
在我的 Item 类中,我添加了这个函数来获取项目:
static func getAllItems() -> NSFetchRequest<Item> {
let request: NSFetchRequest<Item> = Item.fetchRequest() as! NSFetchRequest<Item>
let sortDescriptor = NSSortDescriptor(key: "name", ascending: false)
request.sortDescriptors = [sortDescriptor]
return request
}
在我的 ContentView 我有这个 MasterView 视图:
struct MasterView: View {
@FetchRequest(fetchRequest: Item.getAllItems()) var items: FetchedResults<Item>
@Environment(\.managedObjectContext) var viewContext
var body: some View {
List {
ForEach(items, id: \.self) { item in
NavigationLink(
destination: DetailView(item: item)
) {
Text("\(item.name ?? "")")
}
}.onDelete { indices in
self.items.delete(at: indices, from: self.viewContext)
}
}
}
}
PS我还注意到同步工作缓慢(也许这只是我的情况,不确定)。 我必须在与此代码同步之间等待 5 到 10 秒。 也许有人知道这个等待时间是否正常?
因为从远程同步的数据直接写入持久化,viewContext 不知道它们。 但是我们可以通过 NSPersistentStoreRemoteChangeNotification 得到通知。 fetchHistory,按作者过滤,我们得到同步的数据,然后合并到viewContext,这样UI刷新。
publicDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
publicDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
...
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.automaticallyMergesChangesFromParent = true
context.transactionAuthor = containerName
try? context.setQueryGenerationFrom(.current)
NotificationCenter.default
.publisher(for: .NSPersistentStoreRemoteChange)
// .receive(on: DispatchQueue.main)
.sink { [weak self] notification in
guard let self = self else { return }
self.processRemoteStoreChange(notification)
}
.store(in: &subscriptions)
private func processRemoteStoreChange(_ notification: Notification) {
DispatchQueue(label: "history").async { [weak self] in
guard let self = self else { return }
let backgroundContext = self.container.newBackgroundContext()
backgroundContext.performAndWait { [weak self] in
guard let self = self else { return }
let request = NSPersistentHistoryChangeRequest.fetchHistory(after: self.historyTimestamp)
if let historyFetchRequest = NSPersistentHistoryTransaction.fetchRequest {
historyFetchRequest.predicate = NSPredicate(format: "author != %@", self.containerName)
request.fetchRequest = historyFetchRequest
}
guard let result = try? backgroundContext.execute(request) as? NSPersistentHistoryResult,
let transactions = result.result as? [NSPersistentHistoryTransaction],
transactions.isEmpty == false else {
return
}
foolPrint("transactions = \(transactions)")
self.mergeChanges(from: transactions)
if let timestamp = transactions.last?.timestamp {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.historyTimestamp = timestamp
}
}
}
}
}
private func mergeChanges(from transactions: [NSPersistentHistoryTransaction]) {
context.perform {
transactions.forEach { [weak self] transaction in
guard let self = self, let userInfo = transaction.objectIDNotification().userInfo else { return }
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: userInfo, into: [self.context])
// foolPrint("mergeChanges, \(transaction.timestamp): \(userInfo)")
}
}
}
我在构建应用程序时遇到了类似的问题,所以我构建并发布了 ⛅️ CombineCloudKit 包。 使用 Combine Publishers,可以轻松构建响应 CloudKit 操作的反应式 UI。
https://github.com/chris-araman/CombineCloudKit
我希望人们觉得它有用!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.