This is the fourth in an eight-part series on implementing data sharing in Shopping UK using CloudKit.
Shopping UK is a smart shopping list for UK shoppers. It knows almost every product in the supermarket and will arrange them by aisle. Lists can be shared with family or friends.
Last week, we looked at the three ways records can be retrieved from iCloud, and how the data can be added and changed. Today, we’ll look at how sharing works and how to initiate the sharing of a list.
Before we look at sharing, let’s review how data is segregated in iCloud.
Every iCloud account sees three separate CKDatabases:
Public and Private databases are easy to understand:
Data added to Public can be viewed or modified by anyone (access can be restricted to only users signed into iCloud, or made read-only for everyone except the creator)
Data added to Private can only be seen and changed by the iCloud account holder. No-one else, not even app developers like me, can see your Private database.
The Shared database is more interesting because it doesn’t really contain data. It simply exposes it. Think of it like a database view.
There are three key activities involved in sharing a list:
Once setup, all changes to the shared list will be synchronised between devices.
Today, we’ll look at the first two activities: preparing the data and sending the invite.
List sharing begins when the user chooses the “Share Changes to List” option from the menu:
Before data is exchanged, the app checks:
A message will be shown if either check fails.
The app knows the list is shared if a ShareId is set in the locally cached list record on the device. Later, we’ll see how this is set.
The app creates a UICloudSharingController to manage the interaction with the user. It is possible to manage the interaction with custom code but the UICloudSharingController’s user interface is familiar, widely used in Apple’s own apps, and it works well.
A UICloudSharingControllerDelegate is used with the UICloudSharingController to provide a title and thumbnail image . These are shown in the top-left of the sharing sheet and in the invitation message we’ll see in Step 6.
The PreparationHandler is called when the user selects the method for sending data (e.g. Message, Email, AirDrop, WhatsApp).
The PreparationHandler is responsible for adding the shared records to iCloud.
The new CKRecordZone name is derived from the identity of the list (e.g. “List-db6a6e29-…”).
By placing all records for a shopping list in a dedicated CKRecordZone , it will be easier to delete the data atomically. We’ll revisit this later, in part 7, when we explore what happens when the user stops sharing a list or signs out of iCloud.
Next, the CKRecordZone will be populated with the records that represent the list.
Only two CKRecords are included in the upload message:
The CKShare acts as a window through which other share participants can view the shared records. There are two ways to use it:
In Shopping UK, there is a dedicated CKRecordZone for every shopping list, which means both options are available. But I chose to share via a root record (Option 2) rather than by zone (Option 1) because I thought it may be useful in the future to have records present in the zone that are not part of the share.
As well as defining the scope of visibility, the CKShare is also responsible for recording the owner of the shared records and the acceptance status of all participants. The “Owner” is the iCloud account of the person who initiated the share.
I excluded the list’s items from the initial payload because I encountered problems when sharing very large lists (with several hundred items).
I discovered the upload would take a long time and trigger a timeout. No error was reported but the UICloudSharingController would give up without creating the share. This could be reproduced consistently, and the controller would close after about 10 seconds.
To fix this, I changed strategy to avoid uploading the items upfront. Instead, the items are now added to an internal upload queue, which is processed after sharing has completed (as part of Step 7 below).
This has a nice consequence — simpler logic. The logic to upload list items after the share has been initialised is now the same as the logic to upload list items during normal synchronisation (which will be discussed later, in part 6).
After the PreparationHandler’s completion block has receives the success message from the app, it launches the chosen method of sharing (e.g. Messages)
The user will choose the message’s recipients in the usual way, add additional message contents if they choose, and hit the “Send” button.
Once the invitation message has been sent, the controller calls the delegate’s cloudSharingControllerDidSaveShare method.
Note: I found the cloudSharingControllerDidSaveShare method was sometimes called more than once. This seems to happen when sharing is initiated multiple times in the same session, as if the event handler is being wired-up multiple times and never released. I don’t know if this is something I have done wrong, or a bug in the framework (it’s most likely to be something I’ve done), but I had to work around this by ensuring all operations are idempotent.
Now the data is safely in iCloud, the next task is for the app to link the on-device list to the list in iCloud.
The following properties are sufficient to locate the iCloud data:
CloudKitDatabase = "private" CloudKitZoneName = share.Id.ZoneId.ZoneName CloudKitZoneOwner = share.Id.ZoneId.OwnerName CloudKitShareId = share.Id.RecordName
Finally, the user interface is refreshed so the user knows the list is being shared. The app also triggers an upload of the JournalEntry items (“bread”, “milk”) from the internal upload queue (as described in step 5).
Now sharing is complete:
Next week, we’ll see what happens on the other side, when another device accepts the share.