This is going to be a bit of a technical post.

During WWDC 2019 Apple announced a super simple way of adding iCloud synchronization to your app with NSPersistentCloudKitContainer. The promise is great: simply switch your CoreData stack from a regular NSPersistentContainer to NSPersistentCloudKitContainer and voila, your app will sync 🥳.

For the basic setup, follow this tutorial or check Apples documentation.

lazy var persistentContainer: NSPersistentContainer = {
        let container: NSPersistentContainer = {
            if #available(iOS 13.0, *) {
                return NSPersistentCloudKitContainer(name: "Arya")
            } else {
                // Fallback on earlier versions
                return NSPersistentContainer(name: "Arya")
            }
        }()
        
        let description = container.persistentStoreDescriptions.first
        description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
Snippet that loads a NSPersistentCloudKit container when it is supported

While this basic premise does hold up, I encountered quite some practical issues while adding iCloud sync to my app Hippo. To be honest, it drove me kind of crazy, so much that I almost ditched iCloud sync to move on to something easier 😬

I thought to share some of my biggest struggles here, maybe it will help you with getting iCloud sync into your app!

Issue 1: where is the existing data?

Probably your users will have some data in the app after you add iCloud sync. But NSPersistentCloudKitContainer did not sync existing to iCloud from my app initially.

The issue was that the original NSPersistentContainer did not have history tracking enabled. Thus only newly created objects or updated objects would be pushed to iCloud.

To solve this I implemented an ugly work-around: I've added a isSyncedToiCloud flag to all CoreData entities which defaults to false. Then on launch, the app checks if there are entities that havent been updated and will flip this flag.

This will trigger NSPersistentCloudKitContainer to push the entities to iCloud.

private func batchUpdateiCloudFlag(entityName: String) {
        let batchUpdateRequest = NSBatchUpdateRequest(entityName: entityName)
        let notSyncedPredicate = NSPredicate(format: "isSyncedToiCloud == false")
        batchUpdateRequest.predicate = notSyncedPredicate
        batchUpdateRequest.propertiesToUpdate = ["isSyncedToiCloud":true]
        
        do {
            try context.execute(batchUpdateRequest)
        } catch {
            debugPrint("Error updating iCloud flag \(entityName)")
        }
    }

Issue 2: crash when adding an entity to the model

This issue took me forever to debug.  When you add a new entity to your model, you could get these kind of errors: Constraint unique violation, reason=constraint violation during attempted migration It turns out that this is a bug in NSPersistentCloudKitContainer on iOS 13.

New models should be ordered alphabetically and be added at the bottom of the list 🤦‍♂️. My solution was to prepend a "Z" to the new entity name, but this is quite an ugly solution.

This should be fixed in the latest iOS 14 beta's, but while you support iOS 13 you'll need to be careful with naming new entities.

ZImageData

Issue 3: how to let users enable/disable iCloud sync

Most apps allow users to enable and disable sync in the apps settings screen. Or sync is a premium feature only enabled if you have a subscription.

I tried to implement a setting switch to disable iCloud in Hippo from the app, but it's rather impossible to implement with NSPersistentCloudKitContainer.

First of all, there is a recommended way to turn off syncing, by setting cloudKitContainerOptions to nil. Be sure to do this before you call loadPersistentStores.

if !hippoUserDefaults.isSyncEnabled {
	if #available(iOS 13.0, *) {
	// Disable cloudkit sync https://forums.developer.apple.com/message/392232#392232
		description?.cloudKitContainerOptions = nil
	}
}

container.loadPersistentStores(completionHandler: { (storeDescription, error) in
	if let error = error as NSError? {
		fatalError("Unresolved error \(error), \(error.userInfo)")
	}
})
Snippet that turns iCloud sync on or off depending on isSyncEnabled

But, you will run into issues with this code. Since the container will only be loaded once when the app starts, and its contexts will be used in various views and background processes in your app, it's not simple to change this while your app is running.

I tried for quite some time to get this implemented but gave up after I got a reply from Apple. I now point my users to their iCloud settings in the Settings app to enable or disable sync. Not ideal, but I have no clue how to do this otherwise.

Hippo requesting user to go to Settings for iCloud settings 🥺

iCloud in production

I've submitted Hippo 1.4 with NSPersistentCloudKitContainer to Apple today, hopefully my users can enjoy the benefits of syncing, and I can move on to another feature 😁