Sharing
#
RequirementsYour application must support sharing, make sure your Info.plist file has CKSharingSupported = YES
.
#
Configuring records for sharingYour 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 recordsIf 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 SyncKitYou 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 sharesOnce 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()}
#
SharesFor 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 sharingOn 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 SyncKitSimilar 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)