简体   繁体   中英

SwiftUI CloudKit not refreshing view when remaining active

I am developing a macOS and iOS app with SwiftUI. Both are using CoreData and iCloudKit to sync data between both platforms. It is indeed working very well with the same iCloud Container.

I am facing a problem that the iCloud background update is not being triggered when staying in the application. If I make changes on both systems, the changes are being pushed, however not visible on the other device.

I need to reload the app, close the app and open again or lose focus in my Mac app and come back to it. Then my List is going to be refreshed. I am not sure why it is not working, while staying inside the app without losing focus.

I am read several threads here in Stackoverflow, however they are not working for me. This is my simple View in 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"))
        }
    }
}

In this example I am already using a workaround, with Asperi described in a different question. However, that isn't working for me either. The list is not being refreshed.

In the logs I can see that it is not pinging the iCloud for refreshing. Only when I reopen the app. Why is background modes not working? I have activate everything properly and set up my 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
}()

Edit:

My iOS app only keeps fetching records from iCloud, when the app is being reopened. See this gif:

So apart from my comments and without more information, I suspect you have not set up your project correctly.

Under Signings and Capabilities, your project should look similar to this...

项目签约和能力

As mentioned I suspect a lot of the code in your ContentView view is unnecessary. Try removing the notifications and simplifying your view code, for example...

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

With your project correctly setup, CloudKit should handle the necessary notifications and the @FetchRequest property wrapper will update your data set.

Also, because each Core Data entity is by default Identifiable , there is no need to reference id:\\.self in your ForEach statement, so instead of...

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

you should be able to use...

ForEach(self.persons) { person in

As mentioned in the comments, you have included unnecessary code in your var persistentContainer . It should work as this...

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

For anyone looking at this lately, the fix for me after a LOT of searching was simply adding container.viewContext.automaticallyMergesChangesFromParent = true inside the init(inMemory) method in Persistence.swift (all stock from Apple for a SwiftUI in Xcode 12.5.1. As soon as I added that and rebuilt the 2 simulators, everything sync'd w/in 5-15 seconds as it should.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM