swiftxcodecore-dataicloudnspersistentcloudkitcontainer

Forcing NSPersistentContainer change Core Data


I just added the option for a user to toggle Cloud sync inside my app where I save whether or not the user wants to use iCloud sync in UserDefaults under "useCloudSync". I load my persistentContainer when the app runs with:

class CoreDataManager {
    static let sharedManager = CoreDataManager()
    private init() {}

    lazy var persistentContainer: NSPersistentContainer = {
        var useCloudSync = UserDefaults.standard.bool(forKey: "useCloudSync")

        let containerToUse: NSPersistentContainer?
        if useCloudSync {
           containerToUse = NSPersistentCloudKitContainer(name: "App")
        } else {
            containerToUse = NSPersistentContainer(name: "App")
            let description = containerToUse!.persistentStoreDescriptions.first
            description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
      }

        guard let container = containerToUse, let description = container.persistentStoreDescriptions.first else {
            fatalError("Hey Listen! ###\(#function): Failed to retrieve a persistent store description.")
        }
        description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in

      ...

      return container
   }
}

When a user toggles cloud sync with a UISwitcher, I change the value for "useCloudSyc" in UserDefaults, but the app doesn't use the NSPersistentCloudKitContainer until they force close the app and re-run it again. I would like the container to change right when the user toggles the switch to start loading their data from iCloud.

How can I change to and from a NSPersistentCloudKitContainer when the user toggles "CloudSync"?


Solution

  • Here is possible approach

    extension UserDefaults { // helper key path for observing
        @objc dynamic var useCloudSync: Bool {
            return bool(forKey: "useCloudSync")
        }
    }
    
    class CoreDataManager {
        static let sharedManager = CoreDataManager()
    
        private var observer: NSKeyValueObservation?
        private init() {
        }
    
        lazy var persistentContainer: NSPersistentContainer = {
            setupContainer()
        }()
    
        private func setupContainer() -> NSPersistentContainer {
    
            if nil == observer {
                // setup observe for defults changed
                observer = UserDefaults.standard.observe(\.useCloudSync) { [weak self] (_, _) in
                    try? self?.persistentContainer.viewContext.save() // save anything pending
                    if let newContainer = self?.setupContainer() {
                        self?.persistentContainer = newContainer
                    }
                }
            }
    
            let useCloudSync = UserDefaults.standard.bool(forKey: "useCloudSync")
    
            let containerToUse: NSPersistentContainer?
            if useCloudSync {
                containerToUse = NSPersistentCloudKitContainer(name: "App")
            } else {
                containerToUse = NSPersistentContainer(name: "App")
                let description = containerToUse!.persistentStoreDescriptions.first
                description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
            }
    
            guard let container = containerToUse, let description = container.persistentStoreDescriptions.first else {
                fatalError("Hey Listen! ###\(#function): Failed to retrieve a persistent store description.")
            }
            description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
    
            container.loadPersistentStores { (storeDescription, error) in
                //      ...
            }
    
            return container
        }
    }