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.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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().

I fulfilled my promise to tell you about JavaScript promises!
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.

browser03

Click the sign-in button and login to an iCloud account. Any Apple ID works; it need not be an Apple Developer account.

browser04

After you sign in, the web page will update displayUserName and display the sign-out button.

browser05

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:

browser06

Click Save Acronym and watch your new item appear at the end of the list!

browser07

Go back to the CloudKit Dashboard \ Default Zone and change the sort order to see your new item appear.

dashboard07