[ Music ]
[ Applause ]
Good afternoon and welcome to session 226, What's New in CloudKit.
My name is Paul Seligman.
I'm an engineer on the CloudKit client team and I'm very excited to be with you here today to talk about some updates and new features in the CloudKit ecosystem.
So, what are we going to talk about today?
We're going to start off today with a quick overview of what is CloudKit.
We're then going to switch gears and talk about Telemetry, a new feature which gives you the ability to visualize how your CloudKit-backed applications are behaving.
We're going to talk about some improvements to our APIs and their availabilities.
And we're going to talk about sharing, a new feature which gives your users the ability to share their data while maintaining full control over who has access to it.
So, what is CloudKit?
CloudKit is a technology that gives you the ability to have your application data and your user data available wherever you need it.
CloudKit is a framework which gives you access to a database stored on iCloud.
We use CloudKit extensively inside of Apple.
This gives you the confidence to know that we are committed to it and it gives us the confidence to know that we can scale to hundreds of millions of users.
CloudKit is available on all of Apple's platforms.
Now, I'm going to quickly summarize an introduction to CloudKit, covering topics that we did a few years ago in introduction to CloudKit.
I recommend you go check out that session after this one if you'd like a broader introduction to the ecosystem.
I'd also like to mention these talks which go into more detail about specific aspects of CloudKit.
You can use these to find ways that you and your application can use CloudKit to your advantage.
Now, all the sessions are online and are linked from developer.apple.com/CloudKit.
Here we see the list of objects that every developer using CloudKit needs to be familiar with.
Let's step through them starting with Containers.
A Container is the mechanism by which we silo data up on iCloud.
So, notes uses a Container.
Photos uses a Container and your Applicationm when built on top of CloudKit, will also have access to its own Container.
If we look inside of a Container, we see that Containers contains databases.
And until last week this is our data model.
A Container had two databases, the public and the private.
With the introduction of Sharing, we introduced a third database type, the Shared database.
More about that in a little bit.
The basic unit of storage inside of CloudKit is a record.
A record is a group of key value pairs and typically it maps to an object model, an object in your data model.
Now, we don't store records loosely inside of databases.
Rather, we encapsulate records inside of Record Zones.
Many records can exist inside of a record zone and many Record Zones can exist inside of a database.
Different databases support different types of Record Zones.
The public and private databases have a default record zone.
This is where all your records are going to end up unless you specify otherwise.
Your private database can also contain custom Record Zones which are zones that your application creates and uploads into the database.
And lastly, the new shared database consists of shared Record Zones.
With the introduction of sharing, we're going to add one new core concept to this list.
The concept of a Share.
A Share is a subclass of a Record and as such, lives alongside Records inside of Record Zones.
You can think of a Record as being the thing that you want to share and Share representing how you're going to share it, things like participants and permissions.
Again, we'll get more into that in a little bit.
Just know that it exists.
Now, I mentioned that we use CloudKit extensively inside of Apple and I wanted to take a moment to highlight some of our clients.
In the public database are a couple applications that you've probably used, the WWDC App and the News App.
The News App in particular stores article content, images, etc., in the public database.
And it's a great use of the public database.
Storing content that you want generally accessible to all of your users.
And we can contrast this with the private database.
The private database is where you're going to store the user's private data.
We have several clients of this inside of Apple including iCloud Backup, iCloud Drive, iCloud Photo Library and Notes.
And I'm happy to report that two new features, Notes Collaboration and Activity Sharing are both built on top of CloudKit Sharing.
So, as such, the Notes and Activity Applications are clients of the shared database.
Two years ago we introduced CloudKit by providing two native frameworks, one on iOS and one on macOS.
Last year we extended that family, adding a data framework for tvOS and two web frameworks, CloudKit JS and CloudKit Web Services.
The web frameworks give your users access to CloudKit data, whether they're on the web or on a platform that doesn't have a native framework alternative.
And this year we're going to go ahead and complete the circle by adding a native framework on watchOS.
With this, we now have a native CloudKit framework available across all of Apple's platforms.
Let's take a moment and step through some notable platform-specific changes this year.
Starting with macOS.
The big news that we want to share with you is that you no longer need to distribute your application via the Mac App Store in order to take advantage of CloudKit.
[ Applause ]
Using the new iCloud for Developer ID feature, you can directly entitle your application to use CloudKit and other iCloud services via your permissioning profiles.
Next I want to talk about server to server.
This is a feature that's been out in the wild for a few months now and it's the, gives you the ability to have your servers directly talk to CloudKit servers as administrative users.
Your servers can authenticate themselves to CloudKit using a public/private key pair you've previously established on the CloudKit Dashboard.
And you can set your servers to have full rewrite access to the public database.
This is a great way for you to import your data from your servers into CloudKit or to export data from CloudKit to your servers.
Or to keep two sets of data up-to-date between your servers and CloudKit.
With the introduction of CloudKit as a native framework on watchOS, you now have another mechanism to keep your watch Apps and your iOS Apps up-to-date.
In that way, you can think of CloudKit as an alternative to the watch connectivity framework.
And CloudKit comes with one notable advantage.
That is standalone functionality.
CloudKit uses NSURL session and as a result, we're going to send our network over the best available interface.
If your watch is connected to an iOS device, we'll send traffic over that iOS device.
But the watch is also capable of talking directly to CloudKit servers when it's on Wi-Fi.
Now, we are presenting a full-ish version of the CloudKit API.
With the introduction of CloudKit as the native framework on watchOS, you now have the ability to write similar application code that uses CloudKit across all of Apple's platforms.
The Activity App for example has used this to write similar CloudKit code on iOS and on watchOS to provide the activity sharing future.
Now, notice I said similar code and not identical code.
As you write code and deploy it to the variety of Apple's platforms, you need to keep in mind that the strengths and realities of each platform.
In other words, you're going to hit limited resources in some cases.
You need to keep in mind the CPU characteristics, the storage capacities and the network characteristics such as latency and throughput.
You can use this in determining how often you want to talk to the servers and how much data you're willing to send over the wire.
As always, testing is the best way to tune your App appropriately for the platform and ensure that your users are going to have the best possible experience.
Now I'd like to change gears and talk about Telemetry.
Telemetry is a new feature which allows you to visualize the behavior of your CloudKit-backed applications.
We surface Telemetry as a series of charts which are available on the CloudKit Dashboard.
You can use these charts to visualize your behavior in the public database or an aggregate of your behavior across all users' private databases.
You can scope these charts so that you're viewing data on an hourly, daily, weekly or monthly basis.
And you can choose to view the entirety of your application or scope these charts down to a specific operation type.
So, let's go and see what this looks like.
Here we have the CloudKit Dashboard which you're probably familiar with.
And I want to call your attention to this new UI element in the lower left, the Performance tab.
When you select the Performance tab, you now have access to a series of charts which gives you information about how your clients are behaving.
They fall into two categories.
The first is performance charts and here we surface information such as the number of operations per second and the average size of your requests.
And again, you can scope this so that you're visualizing data in the public or private database along a variety of timescales and potentially on a per-operation type basis.
The other type of chart that we surface is what we call our Correctness chart.
And the one I want to call attention to is client errors.
This tells you what percentage of requests that you have issued that have resulted in a client error.
Now, a client error is a subset of the errors that you might receive from a CKOperation.
And it is that subset which we think your application should be able to resolve and take action on.
So for example, maybe you tried to save a record and there was a conflicting record change upon the server.
Or perhaps you attempted to fetch changes from a Record Zone that the server doesn't know about.
Both of these would be considered client errors and would be surfaced in this chart.
By being able to visualize your error trends, we hope that you can take advantages to find when your client's, situations when your clients are seeing abnormally frequent number of errors.
Now, we've said in the past that error handling is essential for a CloudKit-backed Application.
The difference between an Application that handles errors well and an application that handles errors poorly is the difference between a functioning App and a nonfunctioning App.
It's that serious.
It's an integral part in writing a CloudKit-based Application.
So, we hope that you can use these charts to figure out situations in which you need to go examine how your clients are handling their errors.
For more information on how to handle errors well, I want to invite you to tomorrow's talk, CloudKit Best Practices.
We'll spend some time diving into proper error handling.
Next, I'd like to talk to you about some improvements to our APIs, all of which are new since the last WWDC.
And really, there's four that I want to call your attention to.
Starting with Long-Lived Operations.
Long-Lived Operations give you a mechanism by which you don't have to repeat work that you've already done gets the server.
So, as it stands now, when your application goes away, it exits.
Any operations that were outstanding on behalf of it are torn down.
Even if that operation was moments away from completing.
By making your operations long-lived, your operations can outlive the lifetime of your Application.
They will continue running and CloudKit will continue to cache responses from the server in a local cache.
When your Application is next launched, you've resumed the operation.
And we're just going to go ahead and feed you those caches out of our local cache.
In many cases, this can completely eliminate the need for another network round trip.
We're going to talk about Long-Lived Operations in more detail at tomorrow's talk, Best Practices, 9 am, I hope you can join us.
Next I want to touch on a topic that we've heard of from our developers and it has to do with CKOperation behavior on bad network.
And the picture I want to paint for you here is we've got a device.
The device has network that says it's available but we're not getting any traffic going over in either direction.
And as a side note, you can actually go ahead and replicate the scenario yourself using the network link conditioner, a great developer tool for replicating behavior such as this.
Now, a CKOperation is a subclass of an NS operation.
And as such, as a QualityOfService property.
If your operation is marked as user interactive or user initiated, then on a bad network, we're going to tear it down after 1 minute and give you a network timeout error.
If your operation has any of these other QualityOfServices, we're going to go ahead and continue attempting it for up to seven days.
It might not be what you expected.
What's more, if you choose not to set an explicit QualityOfService on your CKOperation, we will choose one for you and we choose utility.
So, if you add all this up, we get a lot of developer reports saying, you know, it's been 5 days, why is my operation still outstanding.
So, we want to address this and we're going to address this with two new APIs.
The first covers network inactivity and we expose it as the timeout interval for request property on a CKOperation.
It defaults to 1 minute and it's the amount of time that we're willing to wait for a packet to go over the wire.
If we don't hear any traffic received or sent in that amount of time, we're going to tear down your operation to tell you that the network timed out.
We're also going to expose an end to end timeout, and we expose this as the timeout interval for resource property on a CKOperation.
This defaults to seven days and it governs the amount of time that we're willing to wait for an entire network round trip from your device to the service server and its completion back to the device.
Now, I want to make note that a CKOperation may issue multiple network requests as it's going about its job.
So, a CKOperation may take more time than you expect so long as you are making progress in the wire.
Next, I want to talk about how do we efficiently fetch a series of record changes when there are many Record Zones up on the server.
As we'll learn when we get into sharing, your client may see more Record Zones then you've seen in the past.
So, our answer to this used to be that you need to fetch the entire list of Record Zones from a database using a CKFetchRecordZonesOperation.
There's a couple problems with this.
We don't want you to poll and we don't want to have to fetch the entire list of Record Zones down from server.
So, we're no longer going to recommend this for this approach and we're going to replace it with two new concepts.
The first, CKDatabaseSubscription.
This is a new subscription type that will fire whenever any change happens inside of a database.
Even in a Record Zone that you haven't learned about yet.
And we're going to couple that with a CKFetchDatabaseChanges operation.
This is an operation that allows you to ask the server for a list of Record Zones that have pending changes since some point in time in the past.
Okay, so now you have a list of Record Zones that you want to go fetch changes for.
How are you going to do that?
Well, the old way was that you would issue a CKFetchRecordChanges operation.
You'd pass in a single Record Zone and get the changes for that single Record Zone.
We don't want you to have to enumerate, you know, sequentially through all these Record Zones so we've gone ahead and deprecated this operation outright.
And we've replaced it with a brand-new operation with a very similar sounding name, the CKFetchRecordZone changes operation.
This is essentially a batch interface over the old operation and it gives you the ability to fetch record changes across multiple Record Zones in a single network round trip.
So, let's go ahead and visualize this.
Here we have a database, several Record Zones, each Record Zone has a series of records.
And the client, which is up-to-date with all of these changes.
Now, along come a couple of new records.
Your client, by virtue of having previously saved a CKDatabaseSubscription, will cause a push to be generated on the server and sent to the client.
Next, using a CKFetchDatabaseChanges operation, you can ask the server for a list of Record Zones that have pending changes.
In this case, the first and the third.
Now, armed with that list of Record Zones, you can issue a CKFetchRecordZoneChanges operation requesting all those records and all of the change Record Zones in a single network round trip.
And lastly, I'd like to talk about how do you efficiently fetch changes when there are many records sitting in a Record Zone up on the server.
If you've used CloudKit to do this in the past, then you're familiar with the moreComing flag, which was set on a CKFetchRecordChanges operation to inform you that not only have we given you some changes but there are more up on the server that you should go fetch with a subsequent CKFetchRecordChanges operation.
Now, there's a couple problems with this approach.
The first is that we've distributed the logic of check the flag and issue another operation to all of our clients.
It's another potential point of failure.
And secondly, while you're determining that you need to fetch and cue a new operation and doing that in cueing.
CloudKit is sitting around idle.
We want to address both of those so we took advantage of the fact that we made a brand-new operation, CKFetchRecordChanges operation, to change this model.
Instead of us telling you when there are more changes available, you tell us what your intention is via the new fetchAllChanges property.
When this is set to true, then CloudKit will fetch a batch of changes from the server, hand them to your client, and then immediately go back to the server for the next batch of changes.
This allows us to keep the pipeline full, pulling network data over the network while you're processing.
Now, we think that this is going to be such a common behavior that we've gone ahead and we've made this the default behavior for this new class.
So, new CKFetchRecordZoneChanges operations by default will fetch the entirety of records down from a particular Record Zone.
As you might imagine, if you've got a large Record Zone, say, your user's iCloud Photo Library up on the server, this means that the subsequent operation to fetch all records in the Record Zone is going to take a very long time to complete.
We want to make sure that you are resilient in the face of operations that fail part way through.
We don't want to have to go ahead and re-download batches that we've already fetched from the server.
So, we've added a new callback on this new class.
And after we hand you a batch of changes, we're going to go ahead and tell you about an updated server change token.
And your code is going to be responsible for doing two different things.
First, you're going to go ahead and commit all the per record changes that you've received from the server.
And secondly, you're going to go ahead and you're going to cache that server change token.
If the operation fails at some point in the future, you can issue a brand-new CKFetchRecordZoneChanges operation, pass in this locally cached server change token and essentially pick back up where you left off.
No need to re-download the batches of changes that you've already downloaded from the server.
And so these are just 4 of the API improvements that we hope that you can take advantage of as you write applications backed by iCloud, backed by CloudKit.
And with that, I'd like to go ahead and switch gears and invite up Jacob Farkas to walk us through the sharing UI.
[ Applause ]
My name is Jacob Farkas and I'm an engineer on the CloudKit team.
And today I'm going to talk to you about how you can add CloudKit sharing UI to your application by only writing a couple of lines of code.
We've introduced a new class in CloudKit called CKShare.
It's a subclass of CKRecord and it's responsible for storing two important pieces of information.
One, what is shared, and two, what that record is being shared with.
So, let's look at an example of this.
We've got our private database here and we have a note in the private database that we'd like to share.
To do that, we're going to create the CKShare and initialize it using that record as the record.
You always need to create a Share with a root record so there's always something in the Share.
Next, we're going to save that Share and the root record to the server at the same time.
You want to do that because there's a new property on CKRecord that's a reference to the Share that we're creating.
By saving the root record and the Share at the same time, that reference will be set to the share you just created.
So, now we've defined what we want to share but we need to define who we want to share that with.
To do that, we've created a new lookup service in CloudKit.
This lookup service takes a email address and it turns it into a CKShare participant.
You can set the Share participant on the Share, save that Share to the server and now that person's iCloud account has access to the Share.
We also support looking up users via phone numbers or CloudKit user record IDs.
Now, we want to let users have control over what appears in their shared database so we don't want to these records to just instantly appear.
The user should have control so they should be able to accept that Share and join it.
But that means we need a way of telling that other user that we've made a share for them and invited them and that they need to join it.
And we do that via URLs.
Every share has a URL which uniquely identifies it.
If the user taps on this URL in iOS or clicks on it in macOS, we're going to show the accept UI.
We're going to ask them if they want to join the Share.
And if they do, they'll be taken to the App and shown the items in that Share.
The great thing about a URL is that if this user is on an older platform or on a platform that doesn't support sharing, this will take them to iCloud.com.
And we can show them information about the Share and tell them how they can accept it and join the Share.
So, let's put this URL into an email and send it off to the other participant we invited.
They're going to receive the email, click on it and now in their Shared Database they see the Share and the Note that we created and shared with them.
The great thing here is that the Share Database is actually just a view into the owner's private database.
So, if this other user has the right access to the Share, if they update that Note, we're going to see that same change happen in our private database.
So, let's take a look at what this looks like in the UI.
All right, we've got Notes here and we've added sharing to Notes in macOS X Sierra.
By using the same CloudKit sharing APIs that we're making available to all of you today.
So, you'll see that there's a new Share Add Person button up here.
And if we tap on that, we get a new sheet that lets us choose how we want to share that URL.
When we hit Share, the system UI is calling into Notes and telling Notes that it needs to save that Share and the root record to the server.
Once it's done that, the system UI shows a Mail Compose window.
We can invite the other user.
We hit Send.
And the system UI is actually saving that Share to the server, looking up the participants and sending the email off to the other user.
So, if we switch over to our iPad here with the other user, we see the email we just sent.
We can tap on that URL.
And we'll be asked if we want to join the Share.
When we do that, we're launched right into Notes.
The Share shows up, the Note downloads and now we're sharing that Note with the other user.
If I make changes on the Note from the originator, let's say I check off avocados on the list and I add limes as something else to pick up.
We'll see those happen in the note that's being shared to us.
So, let's look at the code behind that.
You're probably all familiar with the CloudKit framework already which is where CKRecord and the new CKShare object live.
If you want to use this new system sharing UI, you're going to find that on macOS in AppKit and in iOS on UIKit.
We'll start by looking at the iOS sharing API.
Before we create a Share, before we bring up the UI, we need to create a Share of course.
So, we'll create a Share here with our record.
We will set a couple properties to let the UI show that Share, title and a thumbnail.
And then we move on to creating a cloud, a UI cloud-sharing controller.
We initialize that with the Share we just made and we pass it a preparation handler.
This preparation handler is going to be called when it's time to save that share in the record to the server.
So, our handler here will create a CKModifyRecords operation.
Save the record and Share to the server and when it's done, it will call the completion handler.
Next, we might want to set some properties on this UI cloud sharing controller.
One of the properties we can set it is the available permissions.
We can say whether we want that Share to be publicly shared only or maybe we only want to give the participants read/write permissions.
We also want to set the present, presentation controller source view so that the pop-up appears in the same place as the button that we tapped to add people.
We'll want to set ourself as a delegate so that we get callbacks about what's happening in the UI.
And finally we call Present.
And when we do that, we're going to get a pop-up that looks something like this.
Now, if you've already saved the Share of the server, you can call UI cloud sharing control with just the Share.
And it will present a list of invited users and let them manage the users on the Share and stop sharing if they'd like.
Everything is taken care of for you by the system UI.
The macOS sharing API is really similar so we're just going to go very quickly and highlight the differences here.
First off, you create an NSItem provider and you register your CloudKit Share with that.
This handler looks the same as what we saw before.
You save the Share in the record to the server and when you're done, you call the completion handler.
Next, you're going to create an NSSharingService.
That sharing service is going to have a delegate that you set yourself and you call perform with the NSItem provider that you created earlier.
Finally, NSSharingService is callback based.
So, if you want to set options on what the share can do, you'll do that with callback like options for Share.
On macOS, the Share create UI will look like this.
And if you want to modify the participants on a Share, it'll look like this.
Next, if a user accepts a Share for your Application, your Application is going to get launched and it'll receive this callback Application user Accepted CloudKit Share.
That callback will contain Share metadata that'll tell you about the Share in the root record that the user just accepted.
It looks really similar on iOS with the exception of using UIApplication instead of NSApplication.
And finally, you need to tell the system that your Application supports CloudKit sharing.
And you do this via the CKSharingSupported key in your info P list.
You can try this all out right now in the CloudKit catalog.
So, I'm going to hand things off now to my colleague, Vanessa, who is going to tell you a little bit more about sharing in depth.
[ Applause ]
Thank you, Jacob.
Hi. And good afternoon.
My name is Vanessa Hong and I'm an engineer on the CloudKit server team.
So, today we will deep dive into sharing by looking at some common use cases.
We'll start with the data that's being shared and then we'll go step-by-step all the way down into the internals of the CKShare object.
Then I'll talk about how you can call our sharing APIs if you want to create your own custom UI.
And then finally we'll close it out with some special notes.
So, let's get started.
Jacob showed how to Share a single record.
But the item the owner wants to share may not be a single record.
It may consist of many records.
Possibly already linked via CKReferences.
And your application may want a participant to see only a subset of these records.
This is why we introduced a new field on the CKRecord called the Parent Reference.
Set the Parent Reference on any records that you wish to be included in the shared hierarchy.
And you can set this up even before the user decides to share.
When the user does share, you will create the CKShare only with the root record.
Then, all of the all the descendent records that are linked to the root record via the Parent Reference are automatically included in the shared hierarchy.
So, let's see what this looks like in the shared database.
A shared database is only a view into the owner's private database.
So, it doesn't contain any physical records.
When a participant accepts a Share, they only see what is shared to them.
So, they see the shared hierarchy.
This means there's no two copies of these records.
There's only one copy and that copy lives in the owner's private DB.
So, this means the owner and all the participants are interacting with the same set of records.
This kind of contention may end up causing conflicts.
To learn how to deal with conflicts, I'd like to refer you to a past WWDC talk called Advanced CloudKit from 2014.
Now, a read/write participant can modify, remove and add records.
But we don't want them to be able to add just anything they want into somebody else's DB.
For instance, they cannot add a random root record.
They also cannot add a record without a Parent Reference, even if it's somehow linked to the shared hierarchy.
So, the correct way to add a new record via the shared database is to set a Parent Reference and link it to the shared hierarchy.
So, even though you're adding a new record for the participant via the shared database, that new record lives in the owner's private DB.
So, what this means is all records that are added by the participant are counted against the owner's quota.
So, the producement's quota is not affected and your developer quota is not affected.
The owner's private database is the only place that we store these records so we can count them only against the owner's quota.
And that's how you share multiple records.
Let's take a closer look at the shared database.
So, here we have two Shares from two different owners but the Shares have the same name, so how do you tell the difference?
Well, we glossed over a very important detail which is that all records in CloudKit live in Zones.
And a Zone is identified by that CKRecord Zone ID.
The Zone name is the name of the custom Zone that you created in the owner's private DB.
An owner name is the owner's user record name.
So, in our example, the two Zones have the same name but different owners.
So, let's say the first owner shares something else but in a different Zone.
So when you call the FetchDatabaseChanges API, you will see this new zone appear.
And then when you call FetchRecordZoneChanges, you'll see the new record and the Share.
Now, let's say the second owner shares something else but in the existing Zone.
Well, this Zone already exists so we won't create a new one.
We'll just reuse it.
When you call the FetchChanges APIs, you will see that this Zone has changed and that there are new records.
And that is our shared database.
So, let's take this one level down and look at the CKShare object.
So, before the owner can create a Share, they must do something to Share.
So, the records describe what to Share.
And the CKShare describes how those records should be shared.
So, we're going to be looking at the how.
So, as Jacob mentioned, every CKShare is a CKRecord but it has some additional properties.
And we've been looking at how these properties apply to the lifecycle of a Share.
So, we're going to start from the beginning and the owner will create a Share.
And the owner has to decide what is the public permission for the Share.
So, in this case the owner says it should be none, because he wants to invite participants.
And let's say he invites two participants.
Their status is automatically invited.
And then the owner decides what permission to give to each participant.
Then, the owner saves the Share and then he gets a URL for the Share.
So, there are two things happening here.
One is that the Share has a state.
And the State says only these two participants can accept the Share.
The owner is the one with the URL and it is his responsibility to tell people about this URL.
So, even if he tells 100 people about this URL, only these two participants can accept the Share.
So, when a participant accepts the Share, they accept via the URL.
And after that accept, their acceptance status becomes accepted.
And then the permission in the Share is exactly what the owner gave them.
So, now let's say the owner wants to create a more open share.
So, let's start over.
The owner sets up a Share and then he decides that the public permission should be readOnly or read/write.
He doesn't add any participants.
He just saves the Share.
And then he gets a URL for the share.
So, there's still two things happening.
One is that the Share has a state and it says anyone can join.
And the owner has a URL.
And it's his responsibility to tell people about it.
So, if he tells 100 people, then all 100 people can join.
So, when they join, they would have to join via the URL and then that participant appears in the Share and accepted state.
Their permission is inherited from the Share's public permission field.
And that's how you set up the Share and accept the Share.
So, the next phase of the Share's lifecycle is when a participant leaves.
And a participant can leave a Share by deleting the CKShare object from their shared DB.
This will also remove the shared records from their shared DB.
So, to be clear, the CKShare still exists.
It exists in the owner's private DB.
It's just that this participant no longer is in the Share in accepted state.
And the owner has full power over his Share so he can remove anybody he wants.
Let's say he wants to remove everybody.
He would do that by deleting the CKShared object from his private database.
This will also remove the pointer from the root record to the Share.
And now the owner is back in the initial state of being unshared.
So, let's move on and talk about the CKShareParticipant object.
So, if you've seen this object before in the lifecycle, you saw the acceptance status and the permission.
But now let's look at the user identity field.
This has a look up info.
And the look up info is how this participant was invited to the share.
So, it will have their email, phone or user record ID.
And the name components are the first and last name and this is populated with when the participant accepts the Share.
Every CKShareParticipant is mapped to an iCloud account.
So, let's say the owner invites 4 participants and we were able to find iCloud accounts for the first two but we couldn't for participant 3 and 4.
This is perfectly okay.
CloudKit will create a temporary placeholder for participant 3 and 4.
And the only people who can accept as participants 3 and 4 are the ones who can prove that they owned the email address or phone number that the owner invited them with.
This is called the verification flow.
This will link the email or phone to their account so that they never have to go through the verification flow again.
And that's all the objects that we have in sharing.
So, now let's move on and talk about sharing APIs.
So, if you want to create your own custom UI, you can call our APIs.
And there are two things that you can do.
So, on the behalf of the owner, you can help them set up the Share.
On behalf of the participant, you can help them accept the Share.
On watchOS and tvOS, there's no built-in system UI.
So, you can ask your user to go to a different platform to set up a Share and accept the Share.
And then the Share data is available across all platforms.
Alternatively, you can just call our sharing APIs.
And this is how you do it.
On behalf of the owner, you can help them add participants.
You would have to look up by email, phone or user record ID and then translate that to a CKShareParticipant object.
Once you have the CKShareParticipant object, add those to the share.
And then call CKModifyRecords operation to save the Share.
Now your application has a URL for the share.
And it is up to you, the application or the owner, to tell people about the URL.
When a participant accepts a Share, we always start with the URL.
You first have to convert the URL to CKShareMetadata object and then pass that metadata object to the CKAcceptShares operation.
Now, the participant will show up in a Share in accepted state.
Now, there are some limitations to the accepted API.
For privacy reasons, we cannot return to you their name components.
And the verification flow is not available.
So, if you get this error or if it has iCloud account Boolean is false, then you can ask your user to open up the URL themselves.
This will trigger the system or the web to take them through the verification flow.
And that's our sharing APIs.
So, now let's talk about your users.
A user of your application can invite anyone they want via any email or any phone number.
Now, what this means is the potential audience for your application is much larger than your current user base.
So, these MIT's may not have installed the latest operating system.
They might not even own an Apple product.
So, when they click on the URL, we take them to the web.
And in the example for Notes, this is what they'll see.
They will be asked to join a Share, after which they'll see the shared Note.
And they can interact with this Note just like they would on a device.
But this is the Notes Web Application that lives on iCloud.com.
What about your Application?
Well, by default, your users will see something like this.
It has your App icon and it asks your user to go on the latest device.
Which is not the ideal user experience.
So, I do have some good news for you.
You can go to your CloudKit Dashboard and configure a fallback URL.
So, when an invitee clicks on a URL that is shared to them, we redirect them to your fallback URL.
We'll append the token that is from the unique URL for the Share so that you can immediately take them to accept the Share and then show them the shared data.
Now, I hope you're excited to get started on sharing.
There is just one last thing that you need to know.
A CKShare is of this new record type and this record type behaves just like any other record type in CloudKit.
You can create custom fields on it.
You can run queries.
You also have the first created in the development environment.
And the easiest way to create it is just log in as a user in your dev environment and share something from your private database.
This will trigger the creation of the record type.
And then go to your CloudKit Dashboard and deploy your scheme into production.
If you don't do this, then users in the production environment may get errors when they create the Share because the record type doesn't exist yet.
And that wraps it up.
So, you learned today that CloudKit is available on all of our platforms including watchOS and is available on the web via CloudKit JS.
Telemetry is available on our CloudKit Dashboard.
It's a great way to visualize your application's behavior including error trends.
There are many API improvements including Long-Lived Operations, [inaudible] and the new fetch changes APIs.
And now you know all about our new feature, sharing.
You've seen this system UI and you know how to create your own custom UI by calling our sharing APIs.
And you've seen all the objects that we used in sharing including the sharers lifecycle.
And I bet you will go back and configure those fallback URLs.
So, I want to thank you for sharing this experience with us.
I want to draw your attention to CloudKit Best Practices.
It's tomorrow at 9 AM.
It's a great session on how to use CloudKit more effectively.
Thank you and enjoy the rest of your WWDC conference.
[ Applause ]