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

标签 ios core-data swiftui cloudkit nspersistentcloudkitcontainer

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

我看了这个 WWDC session about syncing CoreData and CloudKit并在我的 SwiftUI 应用程序中实现了基本同步。它可以工作,数据会发送到所有 3 台设备并自动同步。

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

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

我还想在新数据开始出现时显示一个事件指示器,但不确定在代码中从哪里开始获取获取点?

我用过这个 UIKit sample code由 Apple 提供并将其调整为在 SwiftUI 中工作。我还阅读了所有文档 here .

这是我使用的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 = appTransactionAuthorNamesetQueryGenerationFrom(.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 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)
            }
        }
    }
}

附言
我还注意到同步工作缓慢(也许这只是我的情况,不确定)。我必须在与此代码同步之间等待 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)
    
  • 获取历史并将其合并到 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)")
                }
            }
        }
    

    关于ios - 如何使用 NSPersistentCloudKitContainer 和 SwiftUI 手动获取 CloudKit 数据以更新 UI?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60365179/

    相关文章:

    iOS UITextField addTarget 和 textFieldDidEndEditing 之间的区别

    ios - Swift中使用数组简化代码(核心位置)

    ios - 如何在 TableView 中添加 firebase 值

    ios - 可以使用核心数据在 iOS/核心图形中撤消/重做吗?

    iPhone 核心数据和多线程

    ios - 现在无法以编程方式在 Facebook 上共享文本 IOS

    swift - 获取时出现键值编码兼容错误

    swiftui - 如何将@namespace 传递给 SwiftUI 中的多个 View ?

    swift - 在 SwiftUI 中拥抱 subview

    ios - 如何检测 TextField 何时在 SwiftUI 中变为事件状态?