Ios 如何使用NSPersistentCloudKitContainer和SwiftUI手动获取CloudKit数据以更新UI?

Ios 如何使用NSPersistentCloudKitContainer和SwiftUI手动获取CloudKit数据以更新UI?,ios,core-data,swiftui,cloudkit,nspersistentcloudkitcontainer,Ios,Core Data,Swiftui,Cloudkit,Nspersistentcloudkitcontainer,假设我们有一个工作的NSPersistentCloudKit容器和一个名为Item的CoreData实体。假设我们想要在iOS、iPad和Mac应用程序之间同步 我看了这个,并在我的SwiftUI应用程序中实现了基本同步。它工作正常,数据自动传送到所有3台设备并同步 但在Mac电脑上,如果不重新加载视图(以及模拟器中(因为它们不接收推送通知),数据就不会显示。它会出现,但没有自动刷新。我还将模拟器测试到真实设备上,有时它会自动同步,有时我需要重新打开应用程序以查看更改 我很好奇,是否有一种强制获

假设我们有一个工作的NSPersistentCloudKit容器和一个名为Item的CoreData实体。假设我们想要在iOS、iPad和Mac应用程序之间同步

我看了这个,并在我的SwiftUI应用程序中实现了基本同步。它工作正常,数据自动传送到所有3台设备并同步

但在Mac电脑上,如果不重新加载视图(以及模拟器中(因为它们不接收推送通知),数据就不会显示。它会出现,但没有自动刷新。我还将模拟器测试到真实设备上,有时它会自动同步,有时我需要重新打开应用程序以查看更改

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

我还想在新数据开始出现时显示一个活动指示器,但不确定在代码中从何处找到这个开始获取点

我使用了苹果公司提供的这个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

    }
static func getAllItems()->NSFetchRequest{
让请求:NSFetchRequest=Item.fetchRequest()作为!NSFetchRequest
让sortDescriptor=NSSortDescriptor(键:“名称”,升序:false)
request.sortDescriptors=[sortDescriptor]
退货申请
}
在我的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)
            }
        }
    }
}
struct主视图:视图{
@FetchRequest(FetchRequest:Item.getAllItems())变量items:FetchedResults
@环境(\.managedObjectContext)变量viewContext
var body:一些观点{
名单{
ForEach(items,id:\.self){item in
导航链接(
目的地:详细信息视图(项目:项目)
) {
文本(“\(item.name??)”)
}
}.onDelete{索引在
self.items.delete(at:index,from:self.viewContext)
}
}
}
}
附言。
我还注意到sync运行缓慢(可能这只是我的情况,不确定)。在与此代码同步之间,我必须等待5到10秒。也许有人知道此等待时间是否正常?

因为从远程同步的数据直接写入持久化,viewContext不知道这些数据。但我们可以通过NSPersistentStoreRemoteChangeNotification收到通知。获取历史记录,按作者筛选,我们将获得同步的data,然后合并到viewContext中,以便UI刷新

  • 设置
  • 通知
  • 获取历史并将其合并到viewContext中

  • 我认为应该处理
    .NSPersistentStoreRemoteChange
    通知和强制刷新SwiftUI列表,因为上下文通常在后台队列中以静默方式合并,而UI不会自动处理。以下主题可能会有所帮助[当SwiftUI中的相关实体发生更改时,如何更新@FetchRequest?]()还有。注意将UI刷新从通知处理程序重定向到主队列。@Adelmaer你找到解决方案了吗?我面临着同样的问题。嗨@MikeBernardo,还没有,我想应该是这样的。在真实设备上测试时效果更好。
            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)")
                }
            }
        }