Sharing all container content

I've understood that SwiftData is not abled to share the whole content of a cloudkit database.

So I'm trying to rewrite everything. Does someone knows id Sharing is coming on SwiftData at WWDC 26?

Anyway, can someone can point me an example a a configured coredata stack that share all its content with other icloud users (with sharing pane and accept invitation code).

At this step, on the owner side, I see some data in the default zone of my private container but nothing is visible on the shared zone. Maybe I don't understand where and when I should check shared data in cloudkit console. Need Help also here.

See below by configuration stack:

   // Core Data container
    public lazy var container: NSPersistentContainer = {
        switch delegate.usage() {
        case .preview : return previewContainer()
        case .local : return localContainer()
        case .cloudKit : return cloudKitContainer()
        }
    }()

   private func cloudKitContainer() -> NSPersistentContainer {
        
        let modelURL = delegate.modelURL()
        let modelName = modelURL.deletingPathExtension().lastPathComponent

        guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
            fatalError("Could not load Core Data model from \(modelURL)")
        }
                
        let container = NSPersistentCloudKitContainer(
            name: modelName,
            managedObjectModel: model
        )
        
        let groupIdentifier = AppManager.shared.groupIdentifier
        guard let appGroupURL = FileManager.default.containerURL (
            forSecurityApplicationGroupIdentifier: groupIdentifier
        ) else {
            fatalError("App Group not found: \(groupIdentifier)")
        }
        
        // MARK: - Private Store Configuration
        let privateStoreURL = appGroupURL.appendingPathComponent("\(modelName).sqlite")
        let privateStoreDescription = NSPersistentStoreDescription(url: privateStoreURL)
        
        // Persistent history tracking (MANDATORY)
        privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
        
        // CloudKit options for private database
        // Core Data automatically uses the default zone: com.apple.coredata.cloudkit.zone
        let privateCloudKitOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: delegate.cloudKitIdentifier())
        privateCloudKitOptions.databaseScope = .private
        privateStoreDescription.cloudKitContainerOptions = privateCloudKitOptions
        
        // MARK: - Shared Store Configuration
        
        guard let sharedStoreDescription = privateStoreDescription.copy() as? NSPersistentStoreDescription else {
            fatalError("Create shareDesc error")
        }
        
        // The shared store receives zones that others share with us via CloudKit's shared database
        sharedStoreDescription.url = appGroupURL.appendingPathComponent("\(modelName)-shared.sqlite")
        
        // Persistent history tracking (MANDATORY)
        sharedStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        sharedStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
        
        // CloudKit options for shared database
        // This syncs data from CloudKit shared zones when we accept share invitations
        let sharedCloudKitOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: delegate.cloudKitIdentifier())
        sharedCloudKitOptions.databaseScope = .shared
        sharedStoreDescription.cloudKitContainerOptions = sharedCloudKitOptions
        
        // Configure both stores
        // Private store: com.apple.coredata.cloudkit.zone in private database
        // Shared store: Receives shared zones we're invited to
        container.persistentStoreDescriptions = [privateStoreDescription, sharedStoreDescription]

        container.loadPersistentStores { storeDescription, error in
            
            if let error = error as NSError? {
                fatalError("DB init error:\(error.localizedDescription)")
            } else if let cloudKitContiainerOptions = storeDescription.cloudKitContainerOptions {
                switch cloudKitContiainerOptions.databaseScope {
                case .private:
                    self._privatePersistentStore = container.persistentStoreCoordinator.persistentStore(for: privateStoreDescription.url!)
                case .shared:
                    self._sharedPersistentStore = container.persistentStoreCoordinator.persistentStore(for: sharedStoreDescription.url!)
                default:
                    break
                }
            }
            
            let scope = storeDescription.cloudKitContainerOptions?.databaseScope == .shared ? "shared" : "private"
            print("✅ \(scope) store loaded at: \(storeDescription.url?.path ?? "unknown")")
        }
        
        // Auto-merge
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        
        do {
            try container.viewContext.setQueryGenerationFrom(.current)
        } catch {
            fatalError("Fail to pin viewContext to the current generation:\(error)")
        }
        
        return container
    }

Answered by DTS Engineer in 878048022

You are right that there is no way to share a ClodKit database. An owner can only share a custom record zone in their private database. The default record zone doesn’t support sharing, mentioned here.

To share "the whole content of a cloudkit database," the following may be worth considering :

  • When an owner shares a custom record zone, all the data in the record zone is shared, and so you an share your dataset by putting the dataset into the zone.

  • All your app users have access to the public database of your CloudKit container, and you can build your own user management system to control the access. Based on this, you might be able to "share" your data by using the public database instead.

To get more information about CloudKit sharing, you might go through this sample code.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

You are right that there is no way to share a ClodKit database. An owner can only share a custom record zone in their private database. The default record zone doesn’t support sharing, mentioned here.

To share "the whole content of a cloudkit database," the following may be worth considering :

  • When an owner shares a custom record zone, all the data in the record zone is shared, and so you an share your dataset by putting the dataset into the zone.

  • All your app users have access to the public database of your CloudKit container, and you can build your own user management system to control the access. Based on this, you might be able to "share" your data by using the public database instead.

To get more information about CloudKit sharing, you might go through this sample code.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

I've already played with the samples and was able to share one by one or a set of records. BUT, let me rephrase, I have a set of entities that I want to share ONCE between invited users with ONE sharing at the top. Could you confirm that is it possible?

If Yes, there is probably one missing step or misunderstood in the following code. In my understanding, based on the samples, to do this, I'm doing :

1/ on owner and participant : configure a NSPersistentCloudKitContainer with inside a databaseScope = .private store AND a databaseScope = .shared store

2/ on owner before presenting the UICloudSharingController:

2.1/ create a shared zone-wide share ID

let shareRecordID = CKRecord.ID(recordName: CKRecordNameZoneWideShare, zoneID: zoneID)

2.2/ create or get an existing CKShare with:

func getOrCreateShare() async throws -> CKShare {
  let zoneID = CKRecordZone.ID(zoneName: sharedZoneName)
        
  // Zone-wide share ID
  let shareRecordID = CKRecord.ID(
    recordName:     CKRecordNameZoneWideShare,
    zoneID: zoneID
  )
        
  // Try to fetch existing share
  do {
    let existingShare = try await privateDatabase.record(for: shareRecordID) as? CKShare
    if let share = existingShare {
      print("Existing share retrieved")
      return share
     }
  } catch let error as CKError where error.code == .unknownItem {
                                    
  print("No existing share, creating new one for the entire zone")
  let share = CKShare(recordZoneID: zoneID)
            share[CKShare.SystemFieldKey.title] = "My Sharing Title" as CKRecordValue
            share[CKShare.SystemFieldKey.shareType] = "net.megy.stokk.inventory" as CKRecordValue
            share.publicPermission = .none  // Private only
            
  guard let savedShare = try await privateDatabase.save(share) as? CKShare else {
                throw CKError(.unknownItem)
            }
            
  print("Return new share created for entire zone")
            return savedShare
        }
  // should  not be reached
  throw CKError(.unknownItem)
 }

3/ on the participant side, that I've plugged my SceneDelegate windowScene(_ windowScene:userDidAcceptCloudKitShareWith:) to accep the invitation with:

public func acceptShare(metadata: CKShare.Metadata) async throws {
        try await CKContainer(identifier: cloudKitIdentifier).accept(metadata)
        print("Invitation accepted, syncing...")
    }

I feel like all entities are not routed to my shared zone. Maybe something missing in my xcdatamodeld file.

Would you mind to elaborate what doesn't work with a concrete example model and data? If you don't mind to follow the way the Try out the sharing flow section uses to describe the flow and data you use, and elaborate what you expect to see and don't see in the flow, I'll see if I can figure out why.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

I want to share all the content of a CloudKit database at once between different iCloud accounts. For example, to manage products storage between users. Once the database has been shared or an invitation has been accepted, any additions/modifications/deletions to storage or products must be shared between all users without additional sharing. To be short after the first sharing, everything must be shared without additional new sharing.

I think I can't do this in SwiftData (would be the best solution for me as all the current app is based on SwiftDaa) and that I need to use move to CoreData. Is that correct? Or does a mixed solution may be possible (ie .storage with SWiftData and sharing with CoreData)?

It seems that “Zone Wide Sharing” could be the solution? But I can't find a sufficiently comprehensive example and I wasn't able to make something working. Ultimately, I realize that this is not a very common use case.

With the "ZoneWide Sharing" does it means that we must do a affectedStores or assign(_:to:) to the shared store each time we insert a new managed objects?

If you can I'm ok to share my projet with in private and then I can make a summary of the solution to share here. In all cases, I working on a smallest as possible example app.

If your intent is to have all other users automatically join a share (CKShare) after the share is accepted by just one user, that is not supported.

When you share a CloudKit record zone to a user and the user accepts it, all the records in the zone, no matter being added to the zone before or after the sharing, will be accessible to the user. For other users, however, even the share being public, meaning its publicPermission is more permissive than .none, they still need to go through the acceptance process to join the share. That is as-designed for privacy reason.

In your use case, using CloudKit public database is probably a better bet.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

I'm not sure we understand each other correctly. The goal is for a person to start setting up storage spaces in their home to manage product storage. This person then sends out several invitations (for example, to family members) so that everyone can manage all storage spaces and products with the same rights: adding, modifying, and deleting records.

If I understand correctly, a public database is visible to all users of the application without the need for an invitation. So this is not the solution for my use case?

I want a space shared between a few users brought together by invitations sent out by an initiator. Once these people are together, they work together on all the content in the database, including additions, without the need for new specific invitations.

Sharing all container content
 
 
Q