Skip to main content

Sharing

Requirements#

Your application must support sharing, make sure your Info.plist file has CKSharingSupported = YES.

Configuring records for sharing#

Your model classes can conform to ParentKey and implement its static func parentKey() -> String to return the name of the property pointing at the parent object. For example:

class Company: Object {    ...    let employees = LinkingObjects(fromType: Employee.self, property: "company")}
class Employee: Object, ParentKey {    @objc dynamic var company: Company?    let addresses = LinkingObjects(fromType: Address.self, property: "employee")        static func parentKey() -> String {        return "company"    }}
class Address: Object, ParentKey {    @objc dynamic var employee: Employee?        static func parentKey() -> String {        return "employee"    }}

This way, when SyncKit synchronizes your objects it will set the parent property correctly, so that when you share a Company object with another user the share will include that company's employees and employee addresses.

Updating existing records#

If you specify the parentKey as mentioned above SyncKit will configure correctly the record hierarchy for new records being uploaded, but you might want to update existing records before using sharing functions –if you were using an older version of SyncKit and want to share an object that had been synchronized already, for example. It is possible to do so by doing:


synchronizer.reuploadRecordsForChildrenOf(root: yourRootObject) { error in    if error == nil {        // All records for the root object and its children updated with correct `parent` values    }}

Sharing objects with SyncKit#

You can prepare a CKShare like this:

// Create CKShare and upload records to CloudKitstrongSelf.synchronizer.share(object: object,                              publicPermission: .readOnly,                              participants: [],                              completion: { (share, error) in                                                        ...                        })

This will create the share object and upload the object's record and share to CloudKit. Depending on your application this might be all you need to do, or you could pass this share object to a UICloudSharingController to provide some UI for customization.

To stop sharing, use:

// Delete share on CloudKit and remove local datastrongSelf.synchronizer.removeShare(object: object,                                  completion: { (error) in                                                                    ...                                    })

To update the local copy of a share, or remove it (in response to changes made by a UICloudSharingController, for example) use these:

func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {            guard let share = csc.share else {        return    }    synchronizer.cloudSharingControllerDidSaveShare(share, for: self.editigObject)}    func cloudSharingControllerDidStopSharing(_ csc: UICloudSharingController) {        synchronizer.cloudSharingControllerDidStopSharing(for: self.editingObject)}

Accepting shares#

Once a share has been created and sent to a user your UIApplicationDelegate class must be able to accept that share:

func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShareMetadata) {                let container = CKContainer(identifier: cloudKitShareMetadata.containerIdentifier)        let acceptSharesOperation = CKAcceptSharesOperation(shareMetadatas: [cloudKitShareMetadata])        acceptSharesOperation.qualityOfService = .userInteractive        acceptSharesOperation.acceptSharesCompletionBlock = { error in            if let error = error {                DispatchQueue.main.async {                    let alertController = UIAlertController(title: "Error", message: "Could not accept CloudKit share: \(error.localizedDescription)", preferredStyle: .alert)                    alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))                }            } else {                // Ready to synchronize            }        }        container.add(acceptSharesOperation)    }

At that point the share has been accepted and records can be downloaded from the container's shared database. You can use a synchronizer to do this:

var sharedSynchronizer: CloudKitSynchronizer! = CloudKitSynchronizer.sharedSynchronizer(containerName: "your-container-name", configuration: self.realmConfiguration)
...
sharedSynchronizer.synchronize { error in     }

When user A accepts a share from user B a new record zone is created in user A's shared database for user B's records. As a result, multiple record zones could be created in the shared database. Each time a CloudKitSynchronizer encounters a new record zone it will ask its CloudKitSynchronizerAdapterProvider for a ModelAdapter to handle changes in that record zone. The default provider will create a new Core Data stack or Realm for the record zone, as well as a model adapter. Therefore, one option to access this shared data would be to access each data stack through the synchronizer's adapters (see Architecture)

To make this easier, you can configure a results controller that will surface data obtained from the shared database and will update the results when data gets synchronized. In the case of Core Data, there's CoreDataMultiFetchedResultsController to provide an array of NSFetchedResultsController, and in the case of Realm there's MultiRealmResultsController to provide an array of Results<T> (RealmSwift) or RLMResults (Realm)

// Core Datalet fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "QSCompany")fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]let fetchedResultsController = synchronizer.multiFetchedResultsController(fetchRequest: fetchRequest)fetchedResultsController.delegate = self
...
// Delegatefunc multiFetchedResultsControllerDidChangeControllers(_ controller: CoreDataMultiFetchedResultsController) {    // reload data}    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {    // reload data}
// RealmresultsController = synchronizer.multiRealmResultsController()resultsController.didChangeRealms = { [weak self] controller in    self?.tableView.reloadData()}

Shares#

For existing objects you can check if they have been shared by calling

@objc func share(for object: AnyObject) -> CKShare?

CloudKitSynchronizer will check the model adapter that manages that object and return the share if it exists. You then can present it using a UICloudSharingController, for example.

Record Zone sharing#

On iOS 15 or OSX 12, CloudKit also supports sharing a whole record zone, making it easier to share any number of records in that zone without having to establish a parent-child relationship between them.

Sharing a Record Zone with SyncKit#

Similar to how objects are shared, you can can prepare a CKShare like this:

// Create CKShare and upload records to CloudKitstrongSelf.synchronizer.share(forRecordZoneID: zoneID,                              publicPermission: .readOnly,                              participants: [],                              completion: { (share, error) in                                                        ...                        })

This will create the share object and upload it to CloudKit. Depending on your application this might be all you need to do, or you could pass this share object to a UICloudSharingController to provide some UI for customization.

To stop sharing, use:

// Delete share on CloudKit and remove local datastrongSelf.synchronizer.removeShare(forRecordZoneID: zoneID,                                  completion: { (error) in                                                                    ...                                    })

To update the local copy of a share, or remove it (in response to changes made by a UICloudSharingController, for example) use these:

func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {            guard let share = csc.share else {        return    }    synchronizer.cloudSharingControllerDidSaveShare(share, forRecordZoneID: theRecordZoneID)}    func cloudSharingControllerDidStopSharing(_ csc: UICloudSharingController) {        synchronizer.cloudSharingControllerDidStopSharing(forRecordZoneID: theRecordZoneID)}

The process to accept a share and synchronize the new record zone is then the same as for regular shares. (See Accepting shares)