How to toggle CoreData CloudKit sync during App runtime

Hi,

I'm currently developing a SwiftUI based app with Core Data and CloudKit sync with the NSPersistentCloudKitContainer.

I found different solutions how to toggle CloudKit sync of the Core Data during runtime. The basic idea of these solutions is the following.

  1. instantiate a new NSPersistentCloudKitContainer
  2. set storeDescription.cloudKitContainerOptions = nil
  3. load persistence store

Some solutions recommend to restart the app manually to avoid exactly my problem.

Issues

So far so good. How can I distribute the new viewContext through my app during runtime. In the main App I distributed the viewContext during startup via @Environment(\.managedObjectContext) and it seems not be updated automatically after a reinitialization of NSPersistentCloudKitContainer.

var body: some Scene {
     WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistence.container.viewContext)
        }
}

After deactivating the CloudKit sync I receive the following error when I try to add a new entity.

[error] warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'TestEntity' so +entity is unable to disambiguate.

Any ideas?

Regards

Sven

I further investigated this issue. The problem seems to be in the following line. Here I inject the viewContext into my view hierarchy.

ContentView()
                .environment(\.managedObjectContext, persistence.container.viewContext)

First I deactivate the CloudKit Sync. The persistence container will be reinitialized. Therefore, I get a new instance of the viewContext.

021-12-04 22:08:19.063535+0100 TestApp[67682:1918331] [persistence] Deactivating Core Data CloudKit sync.
2021-12-04 22:08:19.063750+0100 TestApp[67682:1918331] [persistence] Persistence -> viewContext before deactivating cloudKit = <NSManagedObjectContext: 0x60000129d040>
2021-12-04 22:08:19.071889+0100 TestApp[67682:1918331] [persistence] Persistence set to local CoreData.
2021-12-04 22:08:19.102950+0100 TestApp[67682:1918331] [persistence] Persistence -> viewContext after deactivating cloudKit = <NSManagedObjectContext: 0x6000012f4ea0>

Afterwards I add a new test entity to the viewContext. The viewContext stored in the appDelegate has been set correctly. The @Environment(\.managedObjectContext) private var viewContext ones it not updated automatically.

Is there a way to force the update?

2021-12-04 22:08:47.352342+0100 TestApp[67682:1918331] [common] Adding test entitiy.
2021-12-04 22:08:47.352552+0100 TestApp[67682:1918331] [common] ShotResultList -> environment viewContext = <NSManagedObjectContext: 0x60000129d040>
2021-12-04 22:08:47.352699+0100 TestApp[67682:1918331] [common] ShotResultList -> appDelegate viewContext = <NSManagedObjectContext: 0x6000012f4ea0>

I fixed the issue mentioned above by the following changes.

The persistence instance can be reloaded in order switch between NSPersistentCloudKitContainerand NSPersistentContainer. After every reload I increase the attribute persistenceContainerReloaded by one. The line .id(persistence.persistenceContainerReloaded)triggers the reload of the full view hierarchy. Now every view uses the correct viewContext instance.

@main
struct TargetShooterApp: App {
    @StateObject var persistence: Persistence = Persistence.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistence.container.viewContext)
                .id(persistence.persistenceContainerReloaded)
        }
    }
}

Now I faced a new issue. I still see CoreData+CloudKit debug outputs in the terminal, although the storeDescription.cloudKitContainerOptionsis nil. The device is still able to sync local changes to other devices, but is not capable to receive changes made on other devices. After the restart of the app, everything works as expected. It seems, there is still a background process NSCloudKitMirroringDelegate running, uploading CoreData changes.

Any ideas?

Hello! Have you managed to find any solution to this? I've stumbled on the same rock, trying to implement toggle for my iCloud sync in SwiftUI app.

Hi did you manage to fine a solution?

Not really. I found a way to unload and reload my persistence stores. But my App crashes because the old viewContext is still used in the view hierarchy.

Therefore, I tried to completely unload my root view (ContentVIew) by switching to another view. But the ContentView() won't be released from memory, even if it is not shown.

@StateObject private var appRootManager = AppRootManager()

var body: some Scene {
        WindowGroup {
            Group {
                switch appRootManager.currentRoot {
                case .splash:
                    SplashView()
                case .reloading:
                    StatusView()
                case .ready:
                    ContentView()
            }
            .environmentObject(appRootManager)
        }
    }
}
How to toggle CoreData CloudKit sync during App runtime
 
 
Q