SwiftUI CloudKit 在保持活动状态时不刷新视图

问题描述 投票:0回答:3

我正在使用 SwiftUI 开发 macOS 和 iOS 应用程序。两者都使用 CoreData 和 iCloudKit 在两个平台之间同步数据。它确实与同一个 iCloud 容器配合得很好。

我面临一个问题,即停留在应用程序中时不会触发 iCloud 后台更新。如果我在两个系统上进行更改,则会推送更改,但在其他设备上不可见。

我需要重新加载应用程序,关闭应用程序并再次打开,或者在我的 Mac 应用程序中失去焦点并返回它。然后我的

List
就要刷新了。我不知道为什么它不起作用,同时留在应用程序内而不失去焦点。

我在 Stackoverflow 上读到了几个线程,但是它们对我不起作用。这是我在 iOS 中的简单视图

struct ContentView: View {
    
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @State private var refreshing = false
    private var didSave =  NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave)

    @FetchRequest(entity: Person.entity(), sortDescriptors: []) var persons : FetchedResults<Person>
    
    var body: some View {
        NavigationView
        {
            List()
            {
                ForEach(self.persons, id:\.self) { person in
                    Text(person.firstName + (self.refreshing ? "" : ""))
                    // here is the listener for published context event
                    .onReceive(self.didSave) { _ in
                        self.refreshing.toggle()
                    }
                }
            }
            .navigationBarTitle(Text("Person"))
        }
    }
}

在此示例中,我已经使用了一种解决方法,在另一个问题中描述了 Asperi。然而,这对我来说也不起作用。列表未刷新。

在日志中我可以看到它没有 ping iCloud 进行刷新。仅当我重新打开应用程序时。为什么后台模式不起作用?我已正确激活所有内容并设置我的 AppDelegate。

lazy var persistentContainer: NSPersistentCloudKitContainer = {
    /*
     The persistent container for the application. This implementation
     creates and returns a container, having loaded the store for the
     application to it. This property is optional since there are legitimate
     error conditions that could cause the creation of the store to fail.
    */
        
    container.persistentStoreDescriptions.forEach { storeDesc in
        storeDesc.shouldMigrateStoreAutomatically = true
        storeDesc.shouldInferMappingModelAutomatically = true
    }
    //let container = NSPersistentCloudKitContainer(name: "NAME")

    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
             
            /*
             Typical reasons for an error here include:
             * The parent directory does not exist, cannot be created, or disallows writing.
             * The persistent store is not accessible, due to permissions or data protection when the device is locked.
             * The device is out of space.
             * The store could not be migrated to the current model version.
             Check the error message to determine what the actual problem was.
             */
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    
    container.viewContext.automaticallyMergesChangesFromParent = true
    container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
    
    UIApplication.shared.registerForRemoteNotifications()
    
    return container
}()

编辑:

当应用程序重新打开时,我的 iOS 应用程序仅继续从 iCloud 获取记录。看这个动图:

ios swift core-data swiftui icloud
3个回答
3
投票

因此,除了我的评论和没有更多信息之外,我怀疑您没有正确设置您的项目。

在签名和功能下,您的项目应该与此类似......

Project signing and capabilities

如上所述,我怀疑 ContentView 视图中的很多代码都是不必要的。例如,尝试删除通知并简化您的视图代码......

struct ContentView: View {
    
    @Environment(\.managedObjectContext) var managedObjectContext

    @FetchRequest(entity: Person.entity(), 
                  sortDescriptors: []
    ) var persons : FetchedResults<Person>
    
    var body: some View {
        
        NavigationView
        {
            List()
            {
                ForEach(self.persons) { person in
                    Text(person.firstName)
                }
            }
            .navigationBarTitle(Text("Person"))
        }
    }
}

正确设置您的项目后,CloudKit 应该处理必要的通知,并且

@FetchRequest
属性包装器将更新您的数据集。

此外,由于默认情况下每个核心数据实体都是

Identifiable
,因此无需在
id:\.self
语句中引用
ForEach
,因此无需...

ForEach(self.persons, id:\.self) { person in

你应该能够使用...

ForEach(self.persons) { person in

正如评论中提到的,您在

var persistentContainer
中包含了不必要的代码。它应该像这样工作......

lazy var persistentContainer: NSPersistentCloudKitContainer = {
        
    let container = NSPersistentCloudKitContainer(name: "NAME")

    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            // Replace this implementation with code to handle the error appropriately.
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    
    container.viewContext.automaticallyMergesChangesFromParent = true
    container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
            
    return container
}()

2
投票

对于最近关注这个问题的人来说,经过大量搜索后,我的解决方法是简单地在 Persistence.swift 的 init(inMemory) 方法中添加

 container.viewContext.automaticallyMergesChangesFromParent = true
(所有来自 Apple 的 Xcode 12.5.1 中的 SwiftUI 的库存。尽快)当我添加并重建 2 个模拟器时,所有内容都会在 5-15 秒内同步。

init(inMemory: Bool = false) { 
    container = NSPersistentCloudKitContainer(name: "StoreName") 
    container.viewContext.automaticallyMergesChangesFromParent = true

0
投票

我在 macOS 上面临同样的问题。在我停用并再次激活该应用程序之前,该应用程序不会同步。事实证明,这取决于

willBecomeActiveNotification

要强制同步,您可以运行:

NotificationCenter.default.post(.init(name: NSApplication.willBecomeActiveNotification))

或用它安排一些

Timer

当然,这个解决方案并不完美,它实际上是一个解决方法。然而,我花了很多时间试图弄清楚发生了什么,目前我没有看到任何其他解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.