Check Data Re-Sync on App Reinstall with CloudKit (+SwiftUI)

I'm working on an app that is using CoreData with Cloud Kit. I'm able to successfully perform CRUD operations on Entities in the app and I can see the data propagate up to iCloud. I also have an ObservableObject pattern set up so I see changes reflected in the UI when changes happen from other devices to the same data on all devices.


The problem is the use case of someone deleting the application, then re-installing it later. When I test this in Xcode, I can see the app (on first launch) start to sync data back to the app (via console messages from iCloud). But when I run a query and look at the NSFetchResults, I get a count = 0 on the entitiy records I'm looking for (even though they still persist in iCloud post app deletion).


I've tried everything from looking at NSPersistentHistoryChangeRequest in the AppDelegate hoping I would see activity, subscribing to the NSPersistentStoreRemoteChange notification in the content view, etc. While I do see transactions coming in via this method on initial app launch after a re-install (first run in Xcode), my local store still doesn't seem to reflect the new data when I query via the managedObjectContext.


What is the best practice to handle something like this? I've tried pulling apart the code sample from Apple from the WWDC 2019 sessions (Posts app), but as it's not in SwiftUI, some of the design patterns are different.


At this point I'm hoping for some better sessions on this at WWDC 2020 with SwiftUI but hoping someone may have dealt with this before and has a good solution.

The data should still be stored in iCloud and NSPersistentCloudKitContainer should take care of downloading data that is in CloudKit. Could you please post the setup of your Core Data stack?
I'm using an almost identical CoreData stack as to what was shown in the WWDC 2019 Posts demo app. Here's the code:

Code Block
import Foundation
import CoreData
// MARK: - Core Data Stack
/**
The main Core Data stack setup including history processing.
*/
class CoreDataStack {
/**
A persistent container that can load cloud-backed and non-cloud stores.
*/
lazy var persistentContainer: NSPersistentCloudKitContainer = {
// Create a container that can load CloudKit-based stores
let container = NSPersistentCloudKitContainer(name: "MyCoreDataApp")
// Enable history tracking and remote notifications
guard let description = container.persistentStoreDescriptions.first else {
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores(completionHandler: { (_, error) in
guard let error = error as NSError? else { return }
fatalError("###\(#function): Failed to load persistent stores:\(error)")
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.transactionAuthor = appTransactionAuthorName
// Pin the viewContext to the current generation token and set it to keep itself up to date with local changes.
container.viewContext.automaticallyMergesChangesFromParent = true
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
// Observe Core Data remote change notifications.
NotificationCenter.default.addObserver(self, selector: #selector(self.storeRemoteChange), name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
return container
}()



So using this code, I can see the data sync down from iCloud in the output window of Xcode. However, even after the syncing is done, if I try any FetchRequests against my entities using the current managedObjectContext I still get zero results returned. If I quit the app and then re-start it, the FetchRequests return the data as expected.

So I'm not understanding how I can get access to the data that has synced down in the current CoreData managedObjectContext (my app is build with SwiftUI, not Storyboards), and why it is there after I quit the app and re-start. I'm assuming when I quit the app and re-start, the data that synced down is now in the managedObjectContext (current view context) for my app, but I'm not getting how to make that happen on first run. I've tried some things like querying NSPersistentHistoryTransaction and also subscribing to notification and then trying to process history transactions. That kind of works, but it gets messy because you don't know if you'll get them, when they might come in, what state your app might be in before/while they are coming in, etc. Maybe that is the design pattern for this, but it seems like there must be an easier way to handle all of this.
I also was able to get a WWDC 2020 lab appointment for this afternoon to discuss this very issue and a few others around CloudKit and Core Data integrations, syncing the persistent data store, etc., so I'll also post any learnings from that session here too.
Did you ever get resolution on this issue? I am experiencing exactly the same issue.
This issue sounds similar to this one here https://developer.apple.com/forums/thread/125363. The solution on this thread was to sign out of iCloud on the device and sign back in. Seems like it may be related to updating the app via TestFlight.

I was experiencing the same behavior with my app, and the sign out/sign in worked for me.

Maybe this has already been solved or everyone, but this wasn't working for me and it looks like I needed to add mergePolicy and set the automaticallyMergesChangesFromParent on the container's viewContext.

Mine looks like this now:

        container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
        container.viewContext.automaticallyMergesChangesFromParent = true

After removing the app and re-running Xcode the data all synced as expected.

Check Data Re-Sync on App Reinstall with CloudKit (+SwiftUI)
 
 
Q