CoreData data disappears locally after disabling iCloud in device Settings

I've built an app and got it working nicely with CoreData, and now I want to integrate it with CloudKit.

I have made some minor changes to my code (namely updating NSPersistentContainer to NSPersistentCloudKitContainer and CloudKit works - I can see records appearing in the iCloud dashboard and I can modify records from the iCloud dashboard and the changes reflect on my device. Great.

However, if I disable iCloud syncing via Settings on my device (without force-quitting my app) I notice a couple of things:

  • When switching back to my app if I try and edit an entity I sometimes get an app crash

  • If I quit and reload the app all my data has disappeared from my device (it still remains in iCloud).

Is this expected behaviour? Ideally, what I would like, is that the data persists locally if a user has added records but then later decides to disable iCloud syncing on the app.

Here's my code in case I've done anything funky with the implantation. It feels pretty simple, but maybe that's the problem.

CoreDataManager.swift:


class CoreDataManager {
    static let shared = CoreDataManager()

    lazy var persistentContainer: NSPersistentCloudKitContainer = {
        setupPersistentContainer()
    }()

    private func setupPersistentContainer() -> NSPersistentCloudKitContainer {
        print("setup")
        let container = NSPersistentCloudKitContainer(name: "TestApp")

        guard let storeDescription = container.persistentStoreDescriptions.first else {
            fatalError("Could not load store description.")
        }

        storeDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

        container.loadPersistentStores { (_, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error when loading CoreData persistent stores: \(error), \(error.userInfo)")
            }
        }

        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

        return container
    }
}

The CoreDataManager class is then called from my main App.swift file when assigning the MOC:

@main
struct Main: App {
    //
    // MARK: Properties
    //
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    // CoreData context
    let moc = CoreDataManager.shared.persistentContainer.viewContext

    //
    // MARK: Initialisation
    //
    var body: some Scene {
        WindowGroup {
            Test()
                .environment(\.managedObjectContext, moc)
        }
    }
}

And finally the Test.swift (as a very basic example)

struct Test: View {
    @FetchRequest(sortDescriptors: []) var people: FetchedResults<Person>
    
    var body: some View {
        List(people) { person in
            Text(person.name ?? "Unknown")
        }
    }
}

Any sage advice would be welcome as I'm tearing my hair out over this. Is my approach even correct/what's the expected behaviour here?

Replies

I have the same issue, did you find any solution?