编程技术网

关注微信公众号,定时推送前沿、专业、深度的编程技术资料。

 找回密码
 立即注册

QQ登录

只需一步,快速开始

极客时间

如何使用NSPersistentCloudKitContainer和SwiftUI手动获取CloudKit数据以更新UI?:How to fetch CloudKit data manually to update the UI using NSPersistentCloudKitContainer and SwiftUI?

mawenjie8731 云计算 2022-5-10 12:02 6人围观

腾讯云服务器
如何使用NSPersistentCloudKitContainer和SwiftUI手动获取CloudKit数据以更新UI?的处理方法

假设我们有一个正常工作的NSPersistentCloudKitContainer和一个名为Item的CoreData实体.假设我们要在iOS,iPad和Mac应用程序之间进行同步.

Let's say we have a working NSPersistentCloudKitContainer and just one CoreData entity named Item. Let's assume we want to sync between iOS, iPad and Mac app.

我观看了关于同步CoreData和CloudKit的WWDC会话,并实现了我的SwiftUI应用中的基本同步.它可以正常工作,并且数据进入所有3台设备并自动同步.

I watched this WWDC session about syncing CoreData and CloudKit and implemented basic sync in my SwiftUI app. It works and data comes to all 3 devices and syncs automatically.

但是在Mac上,不重新加载视图就不会出现数据(以及在模拟器中(因为它们没有收到推送通知).)它来了,但是没有自动刷新.我也测试了模拟器转换为真实设备,有时会自动同步,有时我需要重新打开应用程序才能查看更改.

But on a Mac the data does not appear without reloading the view (as well as in the simulators (because they don't receive push-notifications). It comes, but there is no automatic refresh. I've also tested simulator to a real device and sometimes it syncs automatically, sometimes I need to reopen the app to see the changes.

我很好奇,是否有一种可以与NSPersistentCloudKitContainer一起使用的强制获取方法.也许有人知道将NSPersistentCloudKitContainer与SwiftUI结合使用时可以手动重新获取数据的解决方法吗?

I'm curious is there a force fetch method that can be used along with NSPersistentCloudKitContainer. Or maybe anyone knows a workaround to Refetch data manually when using NSPersistentCloudKitContainer with SwiftUI?

我还想在新数据开始出现时显示一个活动指示器,但是不确定在哪里可以找到开始获取代码的位置吗?

I also want to show an activity indicator when the new data starts to appear, but not sure where to find this starting getting fetches point in code?

我已使用Apple提供的UIKit 示例代码并将其改编为在SwiftUI中工作.我还在此处阅读了所有文档.

I've used this UIKit sample code provided by Apple and adapted it to work in SwiftUI. I also read all the documentation here.

这是我使用的persistentContainer代码:

Here is the persistentContainer code I use:

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 .但我也尝试过使用它们,并获得相同的结果.不确定是否需要使用它们,因为同步也可以在没有这些调用的情况下进行.

If you see, I didn't include container.viewContext.transactionAuthor = appTransactionAuthorName and setQueryGenerationFrom(.current) and NotificationCenter.default.addObserver in the above code. But I also tried with them and get the same results. Not sure If I need to use them at all, because syncing works without these calls as well.

在我的Item类中,我添加了此功能来获取物品:

In my Item class I've added this function to get the items:

 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视图:

In my ContentView I have this MasterView view:

 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) } } } } 

P.S.我还注意到同步工作缓慢(也许这只是我的情况,不确定).在与此代码同步之间,我必须等待5到10秒.也许有人知道这个等待时间是否正常?

P.S. I also noticed that sync works slowly (maybe this is only my case, not sure). I have to wait from 5 to 10 seconds between syncs with this code. Maybe anyone knows if this waiting time normal or not?

问题解答

由于从远程同步的数据被直接写入持久性,因此viewContext不知道它们.但是我们可以通过NSPersistentStoreRemoteChangeNotification来通知我们.fetchHistory,按作者筛选,我们获取了同步的数据,然后合并到viewContext中,因此UI刷新了.

Because data synced from remote is written directly into persistent, viewContext does't know them. But we can be notified by NSPersistentStoreRemoteChangeNotification. fetchHistory, filter by author, we get the synced data, then merge into viewContext, so UI refreshs.

  1. 设置.
 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) 
  1. 通知.
 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) 
  1. 获取历史记录并将其合并到viewContext中.
 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)") } } } 

这篇关于如何使用NSPersistentCloudKitContainer和SwiftUI手动获取CloudKit数据以更新UI?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程技术网(www.editcode.net)!

腾讯云服务器 阿里云服务器
关注微信
^