Building a Shopping List Application With CloudKit: Sharing Shopping Items

In the previous tutorial of this series, you added the ability to create, update and remove shopping lists from on your iCloud-powered shopping-list app. In the final tutorial of the series, you’ll make use of CKShare to share a specific shopping list item with another user.

Building a Shopping List Application With CloudKit: Sharing Shopping Items

In this series, you have worked with private databases, as well as learning about public databases. However, until WWDC 2016, when Apple introduced CKShare, there was no proper way for apps to share data. Private databases are only available to users who are logged in, whereas public databases are designed for public content and allow anyone to view records. When using some of Apple’s very own iCloud apps, however, such as Pages, Keynote or Notes, you may have noticed by selecting the share button, the ability to invite other users to access your data.

In this post, I’ll show you how to share content selectively.

Building a Shopping List Application With CloudKit: Sharing Shopping Items

In this tutorial, you’ll give your app the same ability, so users can collaborate with you on a shared shopping list item.


Remember that I will be using Xcode 9 and Swift 3. If you are using an older version of Xcode, then keep in mind that you are using a different version of the Swift programming language.

In this tutorial, we continue where we left off in the third tutorial of this series. You can download or clone the project from GitHub.

About Databases

Apple provides three types of databases: public, private, and shared. A public database is where you make available content that everyone can access, all within the default zone. Users don’t need even to be authenticated with iCloud to view the content, but they can’t write changes.

A private database you should be familiar with already, as the shopping app has been leveraging the private database to store records that the public cannot access. Right now, all of your shopping lists are private lists.

A shared database is Apple’s shared database window into your private database, which grants users access to read or write via CKShare.

It’s important to note that the shared database resides on the invited user’s account and is a portal to the owner’s private database, with the CKShare being a particular type of CKRecord that is attached to the CKRecord.

Building a Shopping List Application With CloudKit: Sharing Shopping Items

Adding the UICloudSharingController

You will first implement the UICloudSharingController and its associated UICloudSharingControllerDelegate. By far the easiest approach to sharing is to use UICloudSharingController, which takes care of providing and presenting the screens for sharing your record, inviting users, and setting permissions to your record, all with a few lines of code. According to Apple’s SDK, UICloudSharingController effortlessly enables you to:

  • invite people to view or collaborate on a shared record
  • set the access rights determining who can access the shared record (only people invited or anyone with the share link)
  • set general or individual permissions (read-only or read/write)
  • remove access from one or more participants
  • stop participating (if the user is a participant)
  • stop sharing with all participants (if the user is the owner of the shared record)

In your ListViewController.swift file, include the UICloudSharingControllerDelegate delegate:

class ListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, 

Next, add the TableView’s didSelectRowAt: method so that when a user selects a cell, it summons the sharing sheet.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = items[indexPath.row]
        let share = CKShare(rootRecord: item)

        if let itemName = item.object(forKey: "name") as? String {
            self.itemName = item.object(forKey: "name") as? String
            share[CKShareTitleKey] = "Sharing (itemName)" as CKRecordValue?

        } else {
            share[CKShareTitleKey] = "" as CKRecordValue?
            self.itemName = "item"
        share[CKShareTypeKey] = "com.doronkatz.List" as CKRecordValue
        prepareToShare(share: share, record: item)

In the method above, you create a CKShare, associating the item CKRecord as the root record. The method then sets the share[CKShareTitleKey] and share[CKShareTypeKey] properties in preparation for displaying the shared record. The method finally calls prepareToShare, passing in the original record as well as the just-created shared record, which you will implement next:

private func prepareToShare(share: CKShare, record: CKRecord){
        let sharingViewController = UICloudSharingController(preparationHandler: {(UICloudSharingController, handler: @escaping (CKShare?, CKContainer?, Error?) -> Void) in
            let modRecordsList = CKModifyRecordsOperation(recordsToSave: [record, share], recordIDsToDelete: nil)
            modRecordsList.modifyRecordsCompletionBlock = {
                (record, recordID, error) in
                handler(share, CKContainer.default(), error)
        sharingViewController.delegate = self

        sharingViewController.availablePermissions = [.allowReadWrite,
        self.navigationController?.present(sharingViewController, animated:true, completion:nil)

This method creates the UICloudSharingController within a block handler, by first saving the records using the CKModifyRecordsOperation into your private cloud database, before notifying the completion handler. You also set the available permissions of the view controller, before finally presenting it to your user.

Implementing the UICloudSharingController Delegate Methods

You also need to implement the two mandatory delegate methods, itemTitleForCloudSharingController and failedToSaveShareWithError. There are other optional delegate methods that you may also opt to implement.

func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {
        print("saved successfully")
    func cloudSharingController(_ csc: UICloudSharingController, failedToSaveShareWithError error: Error) {
        print("failed to save: (error.localizedDescription)")
    func itemThumbnailData(for csc: UICloudSharingController) -> Data? {
        return nil //You can set a hero image in your share sheet. Nil uses the default.
    func itemTitle(for csc: UICloudSharingController) -> String? {
        return self.itemName

The itemTitleForCloudSharingController method matches the CKShareTitleKey property you set earlier, requesting the title to display on the invitation list. failedToSaveShareWithError notifies the delegate to handle failed sharing requests. Go ahead and build and run your app, create a record, and then select an individual record to trigger the sharing sheet.

Building a Shopping List Application With CloudKit: Sharing Shopping Items

This looks good, but your job is still not complete! You need to handle how the other users can receive and display your shared record.

Receive and Handle CKShare Records

Finally, you need to set up your app to be able to fetch and display shared records. Open your AppDelegate.swift file and add the userDidAcceptCloudKitShareWith delegate method:

func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShareMetadata) {
        let acceptSharing: CKAcceptSharesOperation = CKAcceptSharesOperation(shareMetadatas: [cloudKitShareMetadata])
        acceptSharing.qualityOfService = .userInteractive
        acceptSharing.perShareCompletionBlock = {meta, share, error in
            print(“successfully shared”)
        acceptSharing.acceptSharesCompletionBlock = {
            error in
            guard (error == nil) else{
                print(“Error (error?.localizedDescription ?? “”)”)
            let viewController: AddItemViewController =
                self.window?.rootViewController as! AddItemViewController
        CKContainer(identifier: cloudKitShareMetadata.containerIdentifier).add(acceptSharing)

UserDidAcceptCloudKitShareWith: notifies the delegate that your app has access to shared information, giving you the opportunity to handle the new content. It notifies you when your app has successfully shared an item, as well as notifying you of issues during the sharing process, through the CKAcceptSharesOperation.

In the method, you create a new instance of the AddItemViewController, calling in a new function that you create, fetchShare, along with the metadata of interest. The method finally adds the CKAcceptSharesOperation to your container. To display the record, open the AddItemViewController.swift file, and implement the fetchShare method:

func fetchShare(_ cloudKitShareMetadata: CKShareMetadata){
        let op = CKFetchRecordsOperation(
            recordIDs: [cloudKitShareMetadata.rootRecordID])
        op.perRecordCompletionBlock = { record, _, error in
            guard error == nil, record != nil else{
                print(“error (error?.localizedDescription ?? “”)”)
            DispatchQueue.main.async {
                self.item = record
        op.fetchRecordsCompletionBlock = { _, error in
            guard error != nil else{
                print(“error (error?.localizedDescription ?? “”)”)

In this final method, you are fetching the shared record and assigning it to your global variable self.item, before finally persisting the operation on your user’s own sharedCloudDatabase database.


In this tutorial, you learned how easy it is, with just a few lines of code, to add the ability to share a subset of your private records with other users and have them collaborate on that very same data. Through the sheer power and simplicity of CKShare, you can select a shopping list item, invite a user to access your item via the UICloudSharingController sheet, and manage user access and permissions.

In addition to sharing your records with users, you also learned how to receive and display data within their apps, as well as storing the record in their shared database.

As you can see, CloudKit provides a lot of convenience and robustness, all within Apple’s ecosystem. Authenticating is virtually invisible, without the need for users to log in with an email or password, and synchronizing is real-time. Beyond this series, I encourage you to explore more advanced topics, such as synchronizing CloudKit with Core Data or creating your caching logic to manage offline data marshaling.