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?
Getting Notification of Changes to the Public Database
When users add or delete items the web page should update to show the current list. Like any respectable CloudKit iOS app, your app can subscribe to the database for updates.
In TIL.js, add the following inside the gotoAuthenticatedState
function, right after self.newItemVisible(true)
:
//1
var querySubscription = {
subscriptionType: 'query',
subscriptionID: userInfo.userRecordName,
firesOn: ['create', 'update', 'delete'],
query: { recordType: 'Acronym', sortBy: [{ fieldName: 'short'}] }
};
//2
publicDB.fetchSubscriptions([querySubscription.subscriptionID]).then(function(response) {
if(response.hasErrors) { // subscription doesn't exist, so save it
publicDB.saveSubscriptions(querySubscription).then(function(response) {
if (response.hasErrors) {
console.error(response.errors[0]);
throw response.errors[0];
} else {
console.log("successfully saved subscription")
}
});
}
});
//3
container.registerForNotifications();
container.addNotificationListener(function(notification) {
console.log(notification);
self.fetchRecords();
});
Here’s what you set up:
- Subscriptions require users to sign in because they use per-user persistent queries, so you set
subscriptionID
touserInfo.userRecordName
. - To avoid firing off the error message triggered by saving a pre-existing subscription, you attempt to fetch the user’s subscription first. If the fetch fails then the subscription doesn’t exist, so it’s safe to save it.
- You register for notifications and add a notification listener that calls
fetchRecords()
to get the new items in the correct sorted order.
Save TIL.js, reload index.html in a browser and sign in. Add a new acronym (perhaps AFK/Away From Keyboard) in the CloudKit dashboard, another browser window, or in the iOS app.
The notification appears in the console and the list of updates on the page! Magic!
Handling Race Conditions
Sometimes the list doesn’t update, even when the notification appears and fetchRecords
successfully completes.
The reason this happens is that race conditions are possible with asynchronous operations, and fetchRecords
sometimes runs before the new item is ready. Try printing records.length
to the console at the end of the performQuery(query)
handler so you can see that this number doesn’t always increase after a notification.
You can mitigate this risk by replacing the first class="row"
div of index.html with the code below to provide a manual refresh button:
<div class="row">
<div class="u-full-width">
<h2>TIL: Today I Learned <small>CloudKit Web Service</small></h2>
</div>
</div>
<div class="row">
<div class="six columns">
<h5 data-bind="text: displayUserName"></h5>
</div>
<div class="four columns">
<div id="apple-sign-in-button"></div>
<div id="apple-sign-out-button"></div>
</div>
<div class="two columns">
<div><button data-bind="click: fetchRecords">Manual Refresh</button></div>
</div>
</div>
Save and reload index.html to see the new button. By the way, it even works without signed in users:
Bonus: Server-Side CloudKit Access
On February 5, 2016 — a week after Facebook announced plans to retire the Parse service — Apple announced that CloudKit now supports server-to-server web service requests, enabling reading and writing to CloudKit databases from server-side processes or scripts.
Talk about a Big Deal: now you can use CloudKit as the backend for web apps that rely on admin processes to update data — like most modern web apps.
However, the API key isn’t enough. You need a server key.
Open Terminal, cd
to the TIL Starter/Server directory, and enter this:
openssl ecparam -name prime256v1 -genkey -noout -out eckey.pem
This creates a server-to-server certificate: eckey.pem
contains the private key.
Still in Terminal, enter the following to display the new certificate’s public key:
openssl ec -in eckey.pem -pubout
In the CloudKit Dashboard, navigate to API Access\Server-to-Server Keys and click Add Server-to-Server Key.
Name the key Server2ServerKey.
Copy the public key from Terminal’s output, paste it into Public Key and tap Save. Then, copy the generated Key ID.
Open config.js in Xcode, and replace my containerIdentifier
and keyID
with your own:
module.exports = {
// Replace this with a container that you own.
containerIdentifier:'iCloud.com.raywenderlich.TIL',
environment: 'development',
serverToServerKeyAuth: {
// Replace this with the Key ID you generated in CloudKit Dashboard.
keyID: '1f404a6fbb1caf8cc0f5b9c017ba0e866726e564ea43e3aa31e75d3c9e784e91',
// This should reference the private key file that you used to generate the above key ID.
privateKeyFile: __dirname + '/eckey.pem'
}
};
To run index.js from a command line, you’ll need to complete a few more steps.
First, go to nodejs.org and install Node.js on your computer if you don’t have it. Next, follow the advice at the end of the setup and add /usr/local/bin to your $PATH, if it’s not there already.
Back in Terminal and still in the TIL Starter/Server directory, run these commands:
npm install
npm run-script install-cloudkit-js
These commands install the npm
module and the CloudKit JS library, which index.js uses.
Now, enter this command in Terminal to run index.js:
node index.js
The output of this command looks similar to the following:
CloudKitJS Container#fetchUserInfo
--> userInfo:
a {
userRecordName: '_a4050ea090b8caace16452a2c2c455f4',
emailAddress: undefined,
firstName: undefined,
lastName: undefined,
isDiscoverable: false }
CloudKitJS CloudKit Database#performQuery { recordType: 'Acronym', sortBy: [ { fieldName: 'short' } ] } {}
--> FOMO: Fear Of Missing Out
Created Sun Jun 19 2016 20:16:32 GMT+1000 (AEST)
...
--> YOLO: You Only Live Once
Created Fri Jun 17 2016 14:37:04 GMT+1000 (AEST)
Done
In here you’ve adapted config.js and the index.js from Apple’s CloudKit Catalog source code to query the TIL public database and print the short
, long
and created
fields.
Where to Go From Here?
Here’s the final version of the web app.
You covered quite a bit in this CloudKit JS tutorial and know the basics of how to use CloudKit JS to make your iOS CloudKit app available to a wider audience via a web interface.
- Using CloudKit JS to access CloudKit Web Services
- Viewing JavaScript log messages in the browser’s console
- Querying the public database to make it visible to everyone
- Authenticating users through iCloud
- Building the web UI to facilitate new entries
- Handling notifications of changes to keep everything in sync
- Supporting server-to-server requests for CloudKit databases
Watch CloudKit JS and Web Services from WWDC 2015, and take some of the features in CloudKit Catalog for a test drive. Explore additional features like user discoverability, record zones and syncToken
.
Watch What’s New with CloudKit from WWDC 2016 for an in-depth look at record sharing and the new record sharing UI — you can try this out in CloudKit Catalog, too. Apple keeps refining CloudKit to make it easier for developers to create reliable apps: look at CKOperation
‘s QualityOfService
to handle long-running operations and CKDatabaseSubscription
and CKFetchDatabaseChanges
to get changes to record zones that didn’t even exist when your app started!
I hope you enjoyed this tutorial — I sure had fun putting it together! Please join the discussion below to share your observations, feedback, ask questions or share your “ah-ha” moments!