6 Replies
      Latest reply on Sep 19, 2019 1:06 PM by nocsi
      tschmitz Level 1 Level 1 (0 points)

        Is there a way to pause or disable syncing via NSPersistentCloudKitContainer at runtime? I want to give my users the option to enable or disable syncing, but there doesn't seem to be a good way to do it. I tried reloading the persistent stores and setting the store description's cloudKitContainerOptions to nil, but if I do that after first initialization, I get an error:

         

        error: Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode

         

        Anyone know of another way to do this?

         

        I submitted a request via Feedback Assistant for a flag to enable or disable syncing (FB6156182). I know the user could theoretically go into the Settings app and enable or disable iCloud access for the app as a whole, but that's not very discoverable, and disables all iCloud access. I'm looking for something a little more fine-grained (limited to the NSPersistentCloudKitContainer) and that I could control in my app's UI.

        • Re: Toggle sync with NSPersistentCloudKitContainer
          .Nick Apple Staff Apple Staff (40 points)

          That error is because you also removed the history tracking option. Which you shouldn't do after you've enabled it.

           

          You can disable CloudKit sync simply by setting the cloudKitContainer options property on your store description to nil.

           

          However, you should leave history tracking on so that NSPersitentCloudKitContainer can catch up if you turn it on again.

            • Re: Toggle sync with NSPersistentCloudKitContainer
              Ash Level 1 Level 1 (0 points)

              How should we go about leaving history tracking enabled once we set the cloudKitContainer options to nil?

              • Re: Toggle sync with NSPersistentCloudKitContainer
                asnow003 Level 1 Level 1 (0 points)

                How do you set the store description to nil.....I see its key/value but I tried to set it on the description after loading the store and it says its read only?

                 

                   // MARK: - Core Data stack

                  

                    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.

                         */

                        let container = NSPersistentCloudKitContainer(name: "name")

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

                          {tried to set it here storeDescription.options = nil}

                  • Re: Toggle sync with NSPersistentCloudKitContainer
                    sdmarcotte Level 1 Level 1 (0 points)

                    What worked for me was setting cloudKitContainerOptions = nil before calling container.loadPersistentStores

                     

                        lazy var persistentContainer: NSPersistentContainer = {

                              .

                              .

                              .

                              if !UserDefaultsManager.shared.syncWithCloudKit {

                                   container.persistentStoreDescriptions.forEach {  $0.cloudKitContainerOptions = nil  }

                              }

                     

                              container.loadPersistentStores( completionHandler: { (_, error) in ... } )

                     

                    This is a hack since this works only when persistentContainer is first initialized.

                     

                    Trying to do this after persistentContainer is initialized would require that I safely teardown and reinitialize the CoreDataStack.  Reinitializing CoreDataStack creates a new managedObjectContext which then must be passed down the view hierarchy and to any services or managers that cache the managedObjectContext.

                     

                    There are many reasons to want to toggle or pause syncing with CloudKit, and since NSPersistentCloudKitContainer is privy to all the inner workings of syncing with CloudKit, NSPersistentCloudKitContainer should be the one to handle this.

                     

                     

                    Some methods I could find useful would be:

                     

                         enum SyncMode {

                              case disabled

                              case paused       // Paus temporarily, useful for batch processing

                              case enabled

                         }

                     

                         persistentContainer.setSyncMode( _ mode: SyncMode )

                     

                         persistentContainer.syncAfter( someFutureTime )     // Sync after some time in the future.

                     

                     

                    Steve

                • Re: Toggle sync with NSPersistentCloudKitContainer
                  ddijitall Level 1 Level 1 (0 points)

                  Did you get this to work? I have a similar need, that is, to offer iCloud storage and sync as a premium feature. I have CloudKit sync working but how do I switch it off? I want to disable CloudKit sync and only enable it when a customer pays for the premium version. I've tried setting the container's cloudKitContainerOptions property to nil but CloudKit sync still happens.

                   

                  Here's my Core Data stack:

                   

                  private lazy var persistentContainer: NSPersistentContainer = {

                        

                          let container = NSPersistentCloudKitContainer(name: modelName)

                        

                          let localDescription = NSPersistentStoreDescription(url: configurationsURL.appendingPathComponent("local.sqlite", isDirectory: false))

                          localDescription.configuration = "Local"

                        

                          let cloudDescription = NSPersistentStoreDescription(url: configurationsURL.appendingPathComponent("cloud.sqlite", isDirectory: false))

                          cloudDescription.configuration = "Cloud"

                          cloudDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: cloudKitContainerIdentifier)

                          cloudDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)

                          cloudDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

                        

                          container.persistentStoreDescriptions = [localDescription, cloudDescription]

                   

                          container.loadPersistentStores { (storeDescription, error) in

                              if let error = error as NSError? {

                                  fatalError("###\(#function): Failed to load persistent stores:\(error)")

                              }

                          }

                        

                          container.viewContext.name = "main"

                          container.viewContext.transactionAuthor = appTransactionAuthorName

                          container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

                          container.viewContext.automaticallyMergesChangesFromParent = true

                        

                          do {

                              try container.viewContext.setQueryGenerationFrom(.current)

                          } catch {

                              fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")

                          }

                        

                          NotificationCenter.default.addObserver(self, selector: #selector(type(of: self).storeRemoteChange(_:)), name: .NSPersistentStoreRemoteChange, object: nil)

                        

                  //        do {

                  //            //try container.initializeCloudKitSchema(options: [.dryRun, .printSchema])

                  //            try container.initializeCloudKitSchema()

                  //        } catch {

                  //            let nserror = error as NSError

                  //            print("Could not initialize CloudKit schema: \(error.localizedDescription)")

                  //            print(nserror.code)

                  //            print(nserror.domain)

                  //        }

                        

                          return container

                      }()

                   

                  Ands here's a function where I'm trying to toggle the CloudKit sync:

                   

                  func enableiCloud(_ isEnabled: Bool) {

                          let cloudDescription = persistentContainer.persistentStoreDescriptions.first() {

                              $0.configuration == "Cloud"

                          }

                          guard cloudDescription != nil else { return }

                          if isEnabled {

                              cloudDescription!.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: cloudKitContainerIdentifier)

                              cloudDescription!.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

                          } else {

                              cloudDescription!.cloudKitContainerOptions = nil

                              cloudDescription!.setOption(false as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

                          }

                      }