Search code examples
swiftuicloudkitnspersistentcloudkitcontainer

I have 2 iOS apps, 2 CloudKit containers, and 1 app needs access to both CloudKit containers. Advice?


I have 2 apps in the App Store, and each uses the private database in its own CloudKit container.
(ie, App1 uses “iCloud.com.company.App1” and App2 uses “iCloud.com.company.App2”)

I want to add a feature to App2 which will require App2 to read/write to the App1 database. To be clear, we’re talking about 2 apps, 2 private databases, but all access occurs under the same user’s AppleID.

In App2, I’ve tried to create 2 NSPersistentCloudKitContainers - one for each App’s database as follows:

@main
struct App2: App {
    @StateObject var app1DB = PersistenceApp1.shared
    @StateObject var app2DB = PersistenceApp2.shared

    @SceneBuilder var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView()
                    .environmentObject(app1DB)
                    .environmentObject(app2DB)
            }
        }
    }
}

…where each Persistence object is defined like this…

class PersistenceApp1: ObservableObject {
    static let shared = PersistenceApp1()

    let container: NSPersistentCloudKitContainer
    
    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: “App1”)     
        // expecting to use CloudKit container id:  “iCloud.com.company.App1”
        …
    }
    …
}

All read & write operations called from App2 using app1DB (unexpectedly) reads & writes the Entity (which was defined in the App1.xcdatamodeld) to show up in “iCloud.com.company.App2”.

So I end up with the Entity duplicated inside “iCloud.com.company.App1” as well as “iCloud.com.company.App2”. None of App2’s reads nor writes actually use “iCloud.com.company.App1” - they all just use the duplicate App1 entity that shows up inside of “iCloud.com.company.App2”.

Looking in CoreData.NSPersistentCloudKitContainer, I see the following comment:

NSPersistentCloudKitContainer managed one or more persistent stores that are backed by a CloudKit private database.

By default, NSPersistentContainer contains a single store description which, if not customized otherwise, is assigned to the first CloudKit container identifier in an application's entitlements.

Instances of NSPersistentCloudKitContainerOptions can be used to customize this behavior or create additional instances of NSPersistentStoreDescription backed by different containers.

Which suggests why I’m seeing this. However, when I look for samples using NSPersistentCloudKitContainerOptions, all I find is references to using the .shared database as promoted via WWDC21-10015 - which focuses on sharing CloudKit data between different users - which is not my use case.

Question 1: does anyone have any ideas on how to configure the NSPersistentCloudKitContainerOptions to accommodate my use case?

Question 2: does anyone want a micro-consulting gig to help me get this working?


Solution

  • First you need to use the model editor to create 2 configurations that contain the entities for each app. If the same entity is in both there is some extra settings that need done when saving and fetching to tell it which store to use.

    Next you need to configure the persistent container with a store for each model configuration:

    let container = NSPersistentCloudKitContainer(name: "CloudKitContainer")
    
    let cloud1 = NSPersistentStoreDescription(url: url1)
    cloud1.configuration = "App1" // matches the name of the model editor configuration
    cloud1.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier:"iCloud.com.company.App1")
    
    let cloud2 = NSPersistentStoreDescription(url: url2))
    cloud2.configuration = "App2"
    cloud2.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier:"iCloud.com.company.App2")
    
    container.persistentStoreDescriptions = [ cloud1, cloud2 ]
    

    If the configurations do not share entities you are all set. If they do then you need to set affectedStores so it only fetches objects from the store you want. Similarly, when saving a new object you need to use assign(_:to:). These are advanced features of Core Data so I would recommend becoming testing this out with 2 local stores first before introducing iCloud syncing.

    Some links that show use of container options:

    Also it might help to mention that NSPersistentContainer is a convenience wrapper around a core data stack which was traditionally took a several lines of code to set up. There is always only one core data stack in an application which means there should also only be one NSPersistentContainer. This is because the single NSManagedObjectContext (which can be in a SwiftUI environment) is designed to work with multiple stores within one core data stack.