NEW: Learn to build the incredible iOS 15 Weather app today! >>

Best practices when iCloud is full / user disables iCloud storage for your app.

Forums > Swift

I am using an NSPersistentCloudKitContainer to sync data. If a user runs out of storage space, or disables iCloud syncing for my app, it seems like it would be best to switch to just using a local store. However, I can't find any guidance on how to do this.

Is this something that I should just let the NSPersistentCloudKitContainer handle for me? Or is there another solution?

For reference, this is how I am setting up the container in my app delegate (the default setup):

lazy var persistentContainer: NSPersistentCloudKitContainer = {
        let container = NSPersistentCloudKitContainer(name: "TrackerModel")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
        return container
    }()

   

Hi @wbrennan899 , I am working thru this now for Core Data. I do have this for just CloudKit on another app so hopefully this helps.

First, it comes down to use case. In my other app where it is just CK and no CD I use it track 'free usage' so that it comes across all devices. Here is the error handling on the completion block for save I use but pretty much lock access if I can't track the usage.

    if let cloudError = error as? CKError {

         if cloudError.errorCode == 9 {
                // handle CloudKit access denied by user settings
              self.handleAccessDeniedByUserSettings()

            }  else if cloudError.errorCode == 25 {

                // handle ExceedQuota
                self.handleExceedPrivateCloudQuote()
            }
      }

I am not sure yet on how Core Data reports this because I'm not sure how to induce these conditions with Core Data but hoping any answer you get addresses that.

In my current use case with Core Data I think I should notify the user that they can't share info across devices as a gentle reminder to change their settings. Just not sure if this same logic above works. Thanks

   

UPDATE:

This is not a good solution. You cannot toggle description.cloudKitContainerOptions without reinitializing the NSPersistentCloudKitContainer.

Also in this case:

  1. user initially has CloudSync on and saves data
  2. user turns off CloudSync
  3. user deletes Cloud data through app settings
  4. user turns CloudSync back on

All of the local user data is lost, which was not my intention.

ORIGINAL:

I figured it out! This thread was helpful: https://developer.apple.com/forums/thread/118924

This solution should allow me to start with iCloud sync off, then I should be able to toggle syncing on/ off it by toggling description.cloudKitContainerOptions = nil for the NSPersistentCloudKitContainer somewhere else in the app if the users turns cloud sync on, or they run out of storage space.


lazy var persistentContainer: NSPersistentCloudKitContainer = {
        let container = NSPersistentCloudKitContainer(name: "ModelName")

        container.persistentStoreDescriptions.forEach {description in
            description.cloudKitContainerOptions = nil
            description.setOption(true as NSNumber,
                         forKey: NSPersistentHistoryTrackingKey)
        }

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in            
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })

        container.viewContext.automaticallyMergesChangesFromParent = true
        return container
    }()

   

@wbrennan899

Hey Will, thanks for posting all of this.

I am also encountering data loss through these four steps you've mentioned. Could you tell me what exactly you did, to fix it?

Thanks!

Best, Kai

   

Update:

I have been able to fix it. I think it's exactly as you described.

Having my persistence container always initialized without CloudKit enabled (with default NSPersistentContainer or description.cloudKitContainerOptions = nil) made it work.

If the user want to use cloudKit, then I will update my container right away with consideration of the device settings.

A snipped:

private func updateICloudSettings() {
        let iCloudToken = FileManager.default.ubiquityIdentityToken

        if iCloudToken == nil {
            UserDefaultsManager.shared.settingICloudSynch = false
        } else {
            if UserDefaultsManager.shared.settingICloudSynch {
                PersistenceManager.defaults.updateContainer()
            }
        }
    }

Thank you:)

   

Hacking with Swift is sponsored by Sentry

SPONSORED With Sentry’s error and performance monitoring for iOS, you see mobile vitals that actually matter, can solve any latency issues quickly, and learn how each release is performing over time.

Learn More

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.