Five Surprises We Encountered while Playing with CloudKit Sharing

Some of our apps at Elemental Tools use iCloud syncing for Core Data which works fine, but you do not want to mess with the database schema too much. Therefore, when we are thinking about future new features that might need database changes we are trying to prototype these at least so far, so that we know what we have to prepare on the database level.

One of these features we might want to add in the future is some kind of sharing based on Apple's CloudKit (which also powers iCloud Core Data syncing). We also looked into Core Data iCloud sharing, but this concept is too limited for our use case in terms of how it works. So we had to dig deeper and take a closer look at CloudKit sharing directly.

As you might know if this topic is of interest for you (if not you probably already stopped reading which is totally ok :-) there are two types of CloudKit sharing:

  1. Hierarchical per-record sharing
  2. Zone-wide sharing of custom zones (which came later)

We decided on zone-wide sharing, since we have not the typical sharing use case in mind and there is no logical hierarchy between the objects we want to share. But most of the things that we came across when playing with CloudKit sharing is relevant for everyone who uses the technology directly (i.e., not via Core Data) and I hope it is useful for some of you. It would at least have been useful for us if someone else would have summed this up already ;-).

"CloudKit allows you to share both record zones and record hierarchies. If you want to share an unbounded collection of records that don’t have natural parent-child relationships, share their containing record zone. However, if you want to share only a specific set of related records, define an explicit record hierarchy and share that instead." [Documentation on CloudKit Shared Records, Apple]

So let's dive into the details. These are the five things that surprised us the most. Not because they do not make sense, but because documentation is often sparse and the answers you find on the Apple Developer Forum or on StackOverflow are of course helpful, but unfortunately sometimes also misleading, outdated and in rare cases also contradictory:

  1. When you create a zone-wide share for a custom zone (naturally it has to be a custom zone) the shared data stays in the private database of the zone's owner. If you think about it this is actually pretty straightforward. However, a lot of documentation talks about the data being "moved" to the shared database -- which is simply wrong. Instead the shared zone in the shared database acts as a kind of virtual "view" into the private database of the shared zone's owner
  2. If a participant in a share has write permissions to a shared zone any records the participant creates will be created in the private database of the shared zone's owner (and only be visible from the shared database for all participants as well). Again pretty logical when you know it, right? But good luck trying to search for this fact on Google or asking ChatGPT...
  3. Most examples about CloudKit sharing use the built-in UI helpers for inviting participants which I would also recommend for standard use cases. It makes you think that doing this manually in code would be hard, but it actually is not. On the contrary, if you use the newer API methods on CKContainer for looking up share participants (fetchShareParticipant) and accepting (fetchShareMetadata & accept) it is really quite simple. If you need the unique invitation URL you can simply get it directly from the SKShare object and don't bother setting the acceptanceStatus of a share participant to pending: the member is now read-only and this is done automatically anyway
  4. Many developers seem to think that a shared zone on the participant side cannot be looked up directly, because some "magic" suffix has been added to the zone ID. Not quite. The "magic" suffix is that the zone owner name has been set to the user record name of the shared zone's owner. So if you know the user record name of the zone owner it is very easy to create a valid record zone ID by using the appropriate initializer
  5. As the user record name is used in many cases you could think that it is also always used for the lastModifiedUserRecordID metadata field in a CloudKit record. Not always. It is set for all other users, but if the current user made the last modification you will have to compare against CKCurrentUserDefaultName which is a CloudKit constant that is also used, e.g., as the zone owner name for record zones in the private database

At least for us, knowing about these few facts helped a lot with finalizing the prototype and answering all the conceptual design questions. Decisions we of course had to make in order to come up with the database fields we might need in the future and for preparing them in our database schema.

The resources that helped us most were:

  • Majid's blog post on "Zone sharing in CloudKit" for getting started
  • The WWDC 21 "What's new in CloudKit" video introducing the newer APIs and zone-wide sharing
  • The "Get the most out of CloudKit sharing" Tech Talk (you can find both videos in the Apple Developer app)
  • Apple's GitHub project which is referenced in the Tech Talk video: https://github.com/apple/sample-cloudkit-sharing

If you are interested in CloudKit sharing I would definitely recommend looking through these resources first. As indicated earlier there is a lot of outdated information out there and many articles only scratch the surface, thus not really adding much value at all. Apples reference documentation about CloudKit is also not bad, but it could be much more specific in a lot of areas. But unfortunately this is not really news, so let's leave it at that ;-).