CloudKit JS Tutorial for iOS
Learn how to use CloudKit JS to create a web app to access the database of a CloudKit iOS app, making your app’s data available on the web! By Audrey Tam.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
CloudKit JS Tutorial for iOS
30 mins
- Prerequisites
- CloudKit JS
- Getting Started
- Setting up CloudKit and the iOS App
- A Word About Knockout and Skeleton
- Configuring CloudKit JS
- Create an API Token
- Error Codes
- Showing the Safari Error Console
- Querying the Public Database
- Authenticating iCloud Users
- Updating the Public Database
- Getting Notification of Changes to the Public Database
- Handling Race Conditions
- Bonus: Server-Side CloudKit Access
- Where to Go From Here?
Authenticating iCloud Users
To add items to the public database, users must sign in to iCloud. Apple handles user authentication directly and provides sign-in and sign-out buttons. If a user has no Apple ID, the sign-in dialogue lets them create one.
In TIL.js, add this code to the end of container.setUpAuth()
, just below the call to fetchRecords()
:
if(userInfo) {
self.gotoAuthenticatedState(userInfo);
} else {
self.gotoUnauthenticatedState();
}
container.setUpAuth()
is a JavaScript promise — the outcome of an asynchronous task. In this case, the task determines whether there’s an active CloudKit session with an authenticated iCloud user. When the task finishes, the promise resolves to a CloudKit.UserIdentity
dictionary or null
, or it rejects to a CloudKit.CKError
object.
When the promise resolves, the CloudKit.UserIdentity
dictionary becomes available to the then
function as the parameter userInfo
. You just added the body of the then
function; if userInfo
isn’t null
, you pass it to gotoAuthenticatedState(userInfo)
; otherwise, you call gotoUnauthenticatedState()
.
Now, you’ll define these two functions, starting with gotoAuthenticatedState(userInfo)
.
Add these lines right above container.setUpAuth().then(function(userInfo) {
:
self.displayUserName = ko.observable('Unauthenticated User');
self.gotoAuthenticatedState = function(userInfo) {
if(userInfo.isDiscoverable) {
self.displayUserName(userInfo.firstName + ' ' + userInfo.lastName);
} else {
self.displayUserName('User Who Must Not Be Named');
}
container
.whenUserSignsOut()
.then(self.gotoUnauthenticatedState);
};
Because you checked Request user discoverability at sign in when you created the API key, users can choose to let the app know their names and email addresses.
If the user isDiscoverable
, the web page will display their name. Otherwise, they’ll be called the User Who Must Not Be Named; while you could display the unique userInfo.userRecordName
returned by the iCloud sign-in, that’d be far less amusing. ;]
Either way, iCloud remembers the user’s choice and doesn’t ask again.
container.whenUserSignsOut()
is another promise — its then
function calls gotoUnauthenticatedState()
.
Right after the code you just inserted, add the following to define gotoUnauthenticatedState()
:
self.gotoUnauthenticatedState = function(error) {
self.displayUserName('Unauthenticated User');
container
.whenUserSignsIn()
.then(self.gotoAuthenticatedState)
.catch(self.gotoUnauthenticatedState);
};
When no user is signed in, you reset displayUserName
and wait for a user to sign in. If the container.whenUserSignsIn()
promise rejects to an error object, the app remains in an unauthenticated state.
Save TIL.js, and go back to index.html in Xcode.
Add the following right after <h2>TIL: Today I Learned <small>CloudKit Web Service</small></h2>
:
<h5 data-bind="text: displayUserName"></h5>
<div id="apple-sign-in-button"></div>
<div id="apple-sign-out-button"></div>
The h5
header creates a text binding to the observable displayUserName
property in TIL.js. To fulfill its promise, container.setUpAuth()
displays the appropriate sign-in/out button.
Save and reload index.html to see Unauthenticated User and the sign-in button.
Click the sign-in button and login to an iCloud account. Any Apple ID works; it need not be an Apple Developer account.
After you sign in, the web page will update displayUserName
and display the sign-out button.
Sign out of iCloud before continuing — the next step will clear userInfo
and display the sign-in button. You’ll feel more in control if you sign out now. :]
Updating the Public Database
You need a web form where users can add new items and some corresponding JavaScript that’ll save items to the public database.
In TIL.js, right after the fetchRecords()
definition, add these lines:
self.newShort = ko.observable('');
self.newLong = ko.observable('');
self.saveButtonEnabled = ko.observable(true);
self.newItemVisible = ko.observable(false);
Here you declare and initialize observable properties that you’ll bind to the UI elements in index.html.
There will be two input fields, newShort
and newLong
and a submit button that you’ll disable when saving the new item.
Only authenticated users can add items to the database, so newItemVisible
controls whether the new-item form is visible. Initially, it’s set to false
.
Add this line to the gotoAuthenticatedState
function right after its open curly brace:
self.newItemVisible(true);
This block makes the new-item form visible after a user signs in.
Add this to the top of the gotoUnauthenticatedState
function:
self.newItemVisible(false);
In here, you’re hiding the new-item form when the user signs out.
Next, add the following to define the saveNewItem
function, right below the self.newItemVisible = ko.observable(false);
line:
self.saveNewItem = function() {
if (self.newShort().length > 0 && self.newLong().length > 0) {
self.saveButtonEnabled(false);
var record = { recordType: "Acronym",
fields: { short: { value: self.newShort() },
long: { value: self.newLong() }}
};
publicDB.saveRecord(record).then(function(response) {
if (response.hasErrors) {
console.error(response.errors[0]);
self.saveButtonEnabled(true);
return;
}
var createdRecord = response.records[0];
self.items.push(createdRecord);
self.newShort("");
self.newLong("");
self.saveButtonEnabled(true);
});
} else {
alert('Acronym must have short and long forms');
}
};
This checks that input fields are not empty, disables the submit button, creates a record and saves it to the public database. The save operation returns the created record, which you push
(append) to items
instead of fetching all the records once again. Lastly, you clear the input fields and enable the submit button.
Save TIL.js, and return to index.html in Xcode.
Add the following right before <div data-bind="foreach: items">
:
<div data-bind="visible: newItemVisible">
<div class="row">
<div class="u-full-width">
<h4>Add New Acronym</h4>
</div>
</div>
<form data-bind="submit: saveNewItem">
<div class="row">
<div class="three columns">
<label>Acronym</label>
<input class="u-full-width" placeholder="short form e.g. FTW" data-bind="value: newShort">
</div>
<div class="nine columns">
<label>Long Form</label>
<input class="u-full-width" placeholder="long form e.g. For the Win" data-bind="value: newLong">
<input class="button-primary" type="submit" data-bind="enable: saveButtonEnabled" value="Save Acronym">
</div>
</div>
</form>
<hr>
</div>
The heart of this code is a web form with two input fields — short
gets three columns and long
gets nine. You also created a submit
button that’s left-aligned with the long
input field. Whenever the submit button is tapped, saveNewItem
is invoked.
For the value bindings, you name the observable properties newShort
and newLong
that saveNewItem
uses. The visible binding will show or hide the web form, according to the value of the observable property newItemVisible
. Lastly, the enable binding enables or disables the submit
button, according to the value of the observable property saveButtonEnabled
.
Save the file and reload index.html in a browser.
Sign in to an iCloud account and the fancy new form should show itself. Try adding a new item, such as YOLO/You Only Live Once:
Click Save Acronym and watch your new item appear at the end of the list!
Go back to the CloudKit Dashboard \ Default Zone and change the sort order to see your new item appear.