An Introduction to iOS 17 CloudKit Sharing

Before the release of iOS 10, the only way to share CloudKit records between users was to store those records in a public database. With the introduction of CloudKit sharing, individual app users can now share private database records with other users.

This chapter aims to provide an overview of CloudKit sharing and the classes used to implement sharing within an iOS app. The techniques outlined in this chapter will be put to practical use in the An iOS CloudKit Sharing Tutorial chapter.

Understanding CloudKit Sharing

CloudKit sharing provides a way for records within a private database to be shared with other app users, entirely at the discretion of the database owner. When a user decides to share CloudKit data, a share link in the form of a URL is sent to the person with whom the data is to be shared. This link can be sent in various ways, including text messages, email, Facebook, or Twitter. When the recipient taps on the share link, the app (if installed) will be launched and provided with the shared record information ready to be displayed.

The level of access to a shared record may also be defined to control whether a recipient can view and modify the record. It is important to be aware that when a share recipient accepts a share, they are receiving a reference to the original record in the owner’s private database. Therefore, a modification performed on a share will be reflected in the original private database.

Preparing for CloudKit Sharing

Before an app can take advantage of CloudKit sharing, the CKSharingSupported key needs to be added to the project Info.plist file with a Boolean true value. Also, a CloudKit record may only be shared if it is stored in a private database and is a member of a record zone other than the default zone.

The CKShare Class

CloudKit sharing is made possible primarily by the CKShare class. This class is initialized with the root CKRecord instance that is to be shared with other users together with the permission setting. The CKShare object may also be configured with title and icon information to be included in the share link message. The CKShare and associated CKRecord objects are then saved to the private database. The following code, for example, creates a CKShare object containing the record to be shared and configured for read-only access:

let share = CKShare(rootRecord: myRecord)
share[CKShare.SystemFieldKey.title] = "My First Share" as CKRecordValue
share.publicPermission = .readOnlyCode language: Swift (swift)

Once the share has been created, it is saved to the private database using a CKModifyRecordsOperation object. Note the recordsToSave: argument is declared as an array containing both the share and record objects:

let modifyRecordsOperation = CKModifyRecordsOperation(
    recordsToSave: [myRecord, share], recordIDsToDelete: nil)Code language: Swift (swift)

Next, a CKConfiguration instance needs to be created, configured with optional settings, and assigned to the operation:

let configuration = CKOperation.Configuration()
        
configuration.timeoutIntervalForResource = 10
configuration.timeoutIntervalForRequest = 10Code language: Swift (swift)

Next, a lambda must be assigned to the modifyRecordsResultBlock property of the modifyRecordsOperation object. The code in this lambda is called when the operation completes to let your app know whether the share was successfully saved:

modifyRecordsOperation.modifyRecordsResultBlock = { result in
    switch result {
    case .success:
        // Handle completion
    case .failure(let error):
        print(error.localizedDescription)
    }
}Code language: Swift (swift)

Finally, the operation is added to the database to begin execution:

self.privateDatabase?.add(modifyRecordsOperation)Code language: Swift (swift)

The UICloudSharingController Class

To send a share link to another user, CloudKit needs to know both the identity of the recipient and the method by which the share link is to be transmitted. One option is to manually create CKShareParticipant objects for each participant and add them to the CKShare object. Alternatively, the CloudKit framework includes a view controller specifically for this purpose. When presented to the user (Figure 51-1), the UICloudSharingController class provides the user with a variety of options for sending the share link to another user:

Figure 51-1

The app is responsible for creating and presenting the controller to the user, the template code for which is outlined below:

let controller = UICloudSharingController { 
	controller, prepareCompletionHandler in

	// Code here to create the CKShare and save it to the database
}

controller.availablePermissions = 
        [.allowPublic, .allowReadOnly, .allowReadWrite, .allowPrivate]

controller.popoverPresentationController?.barButtonItem =
    sender as? UIBarButtonItem

present(controller, animated: true)Code language: Swift (swift)

Note that the above code fragment also specifies the permissions to be provided as options within the controller user interface. These options are accessed and modified by tapping the link in the Collaboration section of the sharing controller (in Figure 51-1 above, the link reads “Only invited people can edit”). Figure 51-2 shows an example share options settings screen:

Figure 51-2

Once the user selects a method of communication from the cloud-sharing controller, the completion handler assigned to the controller will be called. As outlined in the previous section, the CKShare object must be created and saved within this handler. After the share has been saved to the database, the cloud-sharing controller must be notified that the share is ready to be sent. This is achieved by a call to the prepareCompletionHandler method that was passed to the completion handler in the above code. When prepareCompletionHandler is called, it must be passed the share object and a reference to the app’s CloudKit container. Bringing these requirements together gives us the following code:

let controller = UICloudSharingController { controller,
    prepareCompletionHandler in

let share = CKShare(rootRecord: thisRecord)

        share[CKShare.SystemFieldKey.title]
                 = "An Amazing House" as CKRecordValue
        share.publicPermission = .readOnly

        // Create a CKModifyRecordsOperation object and configure it
        // to save the CKShare instance and the record to be shared.
        let modifyRecordsOperation = CKModifyRecordsOperation(
            recordsToSave: [myRecord, share],
            recordIDsToDelete: nil)

        // Create a CKOperation instance
        let configuration = CKOperation.Configuration()

        // Set configuration properties to provide timeout limits
        configuration.timeoutIntervalForResource = 10
        configuration.timeoutIntervalForRequest = 10

        // Apply the configuration options to the operation
        modifyRecordsOperation.configuration = configuration

        // Assign a completion block to the CKModifyRecordsOperation. This will
        // be called the modify records operation completes or fails. 
                     
        modifyRecordsOperation.modifyRecordsResultBlock = { result in
            switch result {
            case .success:
                // The share operation was successful. Call the completion
                // handler
                prepareCompletionHandler(share, CKContainer.default(), nil)
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
        
        // Start the operation by adding it to the database
        self.privateDatabase?.add(modifyRecordsOperation)
}Code language: Swift (swift)

Once the prepareCompletionHandler method has been called, the app for the chosen form of communication (Messages, Mail, etc.) will launch preloaded with the share link. All the user needs to do at this point is enter the contact details for the intended share recipient and send the message. Figure 51-3, for example, shows a share link loaded into the Mail app ready to be sent:

Figure 51-3

Accepting a CloudKit Share

When the recipient user receives a share link and selects it, a dialog will appear, providing the option to accept the share and open it in the corresponding app. When the app opens, the userDidAcceptCloudKitShareWith method is called on the scene delegate class located in the project’s SceneDelegate.swift file:

func windowScene(_ windowScene: UIWindowScene,
    userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
}Code language: Swift (swift)

When this method is called, it is passed a CKShare.Metadata object containing information about the share. Although the user has accepted the share, the app must also accept the share using a CKAcceptSharesOperation object. As the acceptance operation is performed, it will report the results of the process via two result blocks assigned to it. The following example shows how to create and configure a CKAcceptSharesOperation instance to accept a share:

let container = CKContainer(identifier: metadata.containerIdentifier)
let operation = CKAcceptSharesOperation(shareMetadatas: [metadata])     
var rootRecordID: CKRecord.ID!

operation.acceptSharesResultBlock = { result in
    switch result {
    case .success:
        // The share was accepted successfully. Call the completion handler.
        completion(.success(rootRecordID))
    case .failure(let error):
        completion(.failure(error))
    }
}

operation.perShareResultBlock = { metadata, result in
    switch result {
    case .success:
        // The shared record ID was successfully obtained from the metadata.
        // Save a local copy for later. 
        rootRecordID = metadata.hierarchicalRootRecordID

        // Display the appropriate view controller and use it to fetch, and 
        // display the shared record.
        DispatchQueue.main.async {
            let viewController: ViewController = 
                    self.window?.rootViewController as! ViewController
            viewController.fetchShare(metadata)
        }        
    case .failure(let error):
        print(error.localizedDescription)
    }
}Code language: Swift (swift)

The final step in accepting the share is to add the configured CKAcceptSharesOperation object to the CKContainer instance to accept share the share:

container.add(operation) Code language: Swift (swift)

Fetching a Shared Record

Once a share has been accepted by both the user and the app, the shared record needs to be fetched and presented to the user. This involves the creation of a CKFetchRecordsOperation object using the root record ID contained within a CKShare.Metadata instance that has been configured with result blocks to be called with the results of the fetch operation. It is essential to be aware that this fetch operation must be executed on the shared cloud database instance of the app instead of the recipient’s private database. The following code, for example, fetches the record associated with a CloudKit share:

let operation = CKFetchRecordsOperation(
                     recordIDs: [metadata.hierarchicalRootRecordID!])

operation.perRecordResultBlock = { recordId, result in
    switch result {
    case .success(let record):
        DispatchQueue.main.async() {
             // Shared record successfully fetched. Update user 
             // interface here to present to the user. 
        }
    case .failure(let error):
        print(error.localizedDescription)
    }
}

operation.fetchRecordsResultBlock = { result in
    switch result {
    case .success:
        break
    case .failure(let error):
        print(error.localizedDescription)
    }
}

CKContainer.default().sharedCloudDatabase.add(operation)Code language: Swift (swift)

Once the record has been fetched, it can be presented to the user within the perRecordResultBlock code, taking the steps above to perform user interface updates asynchronously on the main thread.

Summary

CloudKit sharing allows records stored within a private CloudKit database to be shared with other app users at the discretion of the record owner. An app user could, for example, make one or more records accessible to other users so that they can view and, optionally, modify the record. When a record is shared, a share link is sent to the recipient user in the form of a URL. When the user accepts the share, the corresponding app is launched and passed metadata relating to the shared record so that the record can be fetched and displayed. CloudKit sharing involves the creation of CKShare objects initialized with the record to be shared. The UICloudSharingController class provides a pre-built view controller which handles much of the work involved in gathering the necessary information to send a share link to another user. In addition to sending a share link, the app must also be adapted to accept a share and fetch the record for the shared cloud database. This chapter has covered the basics of CloudKit sharing, a topic that will be covered further in a later chapter entitled An iOS CloudKit Sharing Tutorial.


Categories