How to read/write to App1’s database from App2?

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?

Accepted Reply

OK, I've solved my issue. 1st of all, defining 2 NSPersistence : ObservableObjects most definitely wasn't the way to go:

    @StateObject var app1DB = PersistenceApp1.shared
    @StateObject var app2DB = PersistenceApp2.shared

I just needed one:

    @StateObject var persistence = Persistence.shared

deeje's answer pointed me in the right direction, but what was still missing for me were the following 3 key ideas:

  1. container's name: is just the name of the .xcdatamodeld - and that this has nothing to do with what I expected (ie, that it was somehow related to my CloudKit container ids “iCloud.com.company.App1”)
		container = NSPersistentCloudKitContainer(name: "Model")
  1. Within Model.xcdatamodeld I needed to define 2 separate configurations - with each configuration holding the Entities that are held within the the CloudKit containers:
       "App1Config" - holds the Entity from iCloud.com.company.App1, and
       "App2Config" - holds the Entity from iCloud.com.company.App2
  1. I needed 2 store descriptions for each app, and to specify the sqlite backing stores to be used for each
		var url1 = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("App1.sqlite")
		let app1DB = NSPersistentStoreDescription(url: url1)
        app1DB.configuration = "App1Config"

		let url2 = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("App2.sqlite")
		let app2DB = NSPersistentStoreDescription(url: url2)
        app2DB.configuration = "App2Config"

        container.persistentStoreDescriptions = [ app1DB, app2DB ]

From there, my App2 simply needs to read/write from/to the Entities as needed - and the container takes care of all the rest. I ❤️ CloudKit!

Replies

use NSPersistentCloudKitContainerOptions to configure the container identifier to sync with

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

    let container: NSPersistentCloudKitContainer

    init(…) {
        container = NSPersistentCloudKitContainer(name: “App1”)

        let store = NSPersistentStoreDescription()
        store.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.example.App1")
        container = [store]

        container.loadPersistentStores…
    }
    …
}

Also, make sure to add both container identifiers to your app's Signing & Capabilities > iCloud > Containers list

OK, I've solved my issue. 1st of all, defining 2 NSPersistence : ObservableObjects most definitely wasn't the way to go:

    @StateObject var app1DB = PersistenceApp1.shared
    @StateObject var app2DB = PersistenceApp2.shared

I just needed one:

    @StateObject var persistence = Persistence.shared

deeje's answer pointed me in the right direction, but what was still missing for me were the following 3 key ideas:

  1. container's name: is just the name of the .xcdatamodeld - and that this has nothing to do with what I expected (ie, that it was somehow related to my CloudKit container ids “iCloud.com.company.App1”)
		container = NSPersistentCloudKitContainer(name: "Model")
  1. Within Model.xcdatamodeld I needed to define 2 separate configurations - with each configuration holding the Entities that are held within the the CloudKit containers:
       "App1Config" - holds the Entity from iCloud.com.company.App1, and
       "App2Config" - holds the Entity from iCloud.com.company.App2
  1. I needed 2 store descriptions for each app, and to specify the sqlite backing stores to be used for each
		var url1 = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("App1.sqlite")
		let app1DB = NSPersistentStoreDescription(url: url1)
        app1DB.configuration = "App1Config"

		let url2 = NSPersistentContainer.defaultDirectoryURL().appendingPathComponent("App2.sqlite")
		let app2DB = NSPersistentStoreDescription(url: url2)
        app2DB.configuration = "App2Config"

        container.persistentStoreDescriptions = [ app1DB, app2DB ]

From there, my App2 simply needs to read/write from/to the Entities as needed - and the container takes care of all the rest. I ❤️ CloudKit!