Creating a Database Schema by Saving Records

During development, it’s easy to create a schema using CloudKit APIs. When you save record objects to a database, the associated record types and their fields are automatically created for you. This feature is called just-in-time schema and is available only when you use the development environment which is not accessible by apps sold on the store. For example, during development you can populate a CloudKit database with test records stored in a property list.

This chapter introduces some CloudKit APIs and contains code fragments. Add #import <CloudKit/CloudKit.h> at the top of each implementation file that uses CloudKit classes and methods. Read Cloud Kit Framework Reference for details on CloudKit APIs.

About Designing Your Schema

Design the CloudKit schema to store the portions of your app’s object model that you want to persist. If you are implementing an app from scratch, use the Model-View-Controller design pattern to separate the user interface views from the model objects. Then design a schema that efficiently stores the portions of the object model you want to store in iCloud.

Separate Data into Record Types

A CloudKit schema consists of one or more record types that have a name, fields, and other metadata. The field types are similar to the allowable property list types with some additions. Use a specialized Asset type for bulk data that is stored separately from records, a Location type for efficiently querying geographical coordinates, and a Reference type to represent one-to-one and one-to-many relationships among records. The maximum size of a record is 1MB, so use the Asset type (not the Bytes type) for large data.

The table shows possible field types, as they appear in CloudKit Dashboard, and their equivalent CloudKit framework classes.

Field Type

Class

Description

Asset

CKAsset

A large file that is associated with a record but stored separately

Bytes

NSData

A wrapper for byte buffers that is stored with the record

Date/Time

NSDate

A single point in time

Double

NSNumber

A double

Int(64)

NSNumber

An integer

Location

CLLocation

A geographical coordinate and altitude

Reference

CKReference

A relationship from one object to another

String

NSString

An immutable text string

List

NSArray

Arrays of any of the above field types

Design record types using these fields to store your app’s persistent data. For example, this sketch of a master-detail user interface displays a collection of artwork titles in the master interface (column on the left) and properties of the selected artwork in the detail interface (area on the right).

../Art/masterdetail_sketch_2x.png

In the code, the underlying object model consists of an Artwork and Artist class where Artwork has a to-one relationship to Artist.

In the schema, there’s a one-to-one mapping between the objects in this object model and the record types Artwork and Artist. The artist field of the Artwork record type is a Reference type with a reference to an Artist record. The image field is an Asset type containing a URL, and the location field is a Location type with longitude and latitude properties. All other fields in Artwork and Artist are simple String and Date types.

../Art/schema_design_2x.png

Decide on Names for Your Records

Decide on a heuristic for creating unique names for your records. The record name coupled with a record zone (a partition of a database) is the record identifier that represents the location of a record in a database. The record name can be a foreign key used by another data source or a combination of strings that makes it unique within a record zone. For example, the record name of an Artwork record can combine an artist’s first and last name with a catalog number, as in the string 115 Chen, Mei. If you create records using CloudKit Dashboard, a unique ID is automatically assigned to the record.

Create Records Programmatically

First create a record identifier, an instance of the CKRecordID class, specifying the record name and record zone. Then create a record, an instance of the CKRecord class, passing the record identifier. Set the record’s fields using key-value coding style methods.

To create a record in code

  1. Create a record ID specifying a unique record name.

    let artworkRecordID = CKRecordID(recordName: "115")
    CKRecordID *artworkRecordID = [[CKRecordID alloc] initWithRecordName:@"115"];
  2. Create a record object.

    let artworkRecord = CKRecord(recordType: "Artwork", recordID: artworkRecordID)
    CKRecord *artworkRecord = [[CKRecord alloc] initWithRecordType:@"Artwork" recordID:artworkRecordID];
  3. Set the record’s fields.

    artworkRecord["title"] = "MacKerricher State Park" as NSString
    artworkRecord["artist"] = "Mei Chen" as NSString
    artworkRecord["address"] = "Fort Bragg, CA" as NSString
    artworkRecord[@"title" ] = @"MacKerricher State Park";
    artworkRecord[@"artist"] = @"Mei Chen";
    artworkRecord[@"address"] = @"Fort Bragg, CA";

Save Records

First select a database where you will save the records (public, private, or custom) and then save the record. If a record type doesn’t exist for the record, it is created for you.

To save a record

  1. Get the database in your app’s default container.

    To get the public database:

    let myContainer = CKContainer.default()
    let publicDatabase = myContainer.publicCloudDatabase
    CKContainer *myContainer = [CKContainer defaultContainer];
    CKDatabase *publicDatabase = [myContainer publicCloudDatabase];

    To get the private database:

    let myContainer = CKContainer.default()
    let privateDatabase = myContainer.privateDatabase
    CKContainer *myContainer = [CKContainer defaultContainer];
    CKDatabase *privateDatabase = [myContainer privateCloudDatabase];

    To get a custom container:

    let myContainer = CKContainer(identifier: "iCloud.com.example.ajohnson.GalleryShared")
    CKContainer *myContainer = [CKContainer containerWithIdentifier:@"iCloud.com.example.ajohnson.GalleryShared"];

    To create a custom container shared by multiple apps, read Share Containers Between Apps.

  2. Save the record.

    publicDatabase.save(artworkRecord) {
        (record, error) in
        if let error = error {
            // Insert error handling
            return
        }
        // Insert successfully saved record code
    }
    [publicDatabase saveRecord:artworkRecord completionHandler:^(CKRecord *artworkRecord, NSError *error){
       if (error) {
          // Insert error handling
          return
       }
       // Insert successfully saved record code
    }];

    If the record type doesn’t exist, CloudKit framework creates it with the fields you set.

Before clicking the Run button in Xcode, enter iCloud credentials on the device, as described in the next section.

Enter iCloud Credentials Before Running Your App

In development, when you run your app through Xcode on a simulator or a device, you need to enter iCloud credentials to read records in the public database. In production, the default permissions allow non-authenticated users to read records in the public database but do not allow them to write records.

Therefore, before you run your app and save records to the database, enter an iCloud account in Settings on iOS or System Preferences on a Mac. Also enable iCloud Drive. Later, write the necessary error handling to present a dialog to the user when iCloud credentials are needed, as described in Alert the User to Enter iCloud Credentials.

To run your app in iOS Simulator, enter the iCloud credentials in iOS Simulator before you select the simulator and click the Run button in Xcode. You need to perform these steps for each iOS Simulator you select in the Scheme pop-up menu in Xcode.

To enter iCloud credentials in iOS Simulator

  1. Choose Xcode > Open Developer Tool > iOS Simulator.

  2. In iOS Simulator, choose Hardware > Home.

  3. Launch the Settings app and click iCloud.

    ../Art/3_icloud_settings_2x.png
  4. Enter an Apple ID and password.

  5. Click Sign In.

    Wait while iOS verifies the iCloud account.

  6. To enable iCloud Drive, click the iCloud Drive switch.

    If the switch doesn’t appear, iCloud Drive is already enabled.

For how to create an iCloud account, read Create an iCloud Account for Development.

Alert the User to Enter iCloud Credentials

Improve the user’s experience by verifying that the user is signed in to their iCloud account before saving records. If the user is not signed in, present an alert instructing the user how to enter their iCloud credentials and enable iCloud Drive. Insert your code that saves records in the else clause below.

[[CKContainer defaultContainer] accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) {
    if (accountStatus == CKAccountStatusNoAccount) {
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Sign in to iCloud"
                                                                       message:@"Sign in to your iCloud account to write records. On the Home screen, launch Settings, tap iCloud, and enter your Apple ID. Turn iCloud Drive on. If you don't have an iCloud account, tap Create a new Apple ID."
                                                                preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"Okay"
                                                  style:UIAlertActionStyleCancel
                                                handler:nil]];
        [self presentViewController:alert animated:YES completion:nil];
    }
    else {
        // Insert your just-in-time schema code here
    }
}];

To learn about errors that may occur when saving records, read CloudKit Framework Constants Reference.

Run Your App

In Xcode, run your app to execute the code that saves records and creates the schema in the database.

Verify Your Steps

Use CloudKit Dashboard to verify that the record types were added to the schema and that the records were added to the database.

View Record Types by Using CloudKit Dashboard

Verify that the record types have the correct field names and types.

To view record types

  1. Sign in to CloudKit Dashboard.

  2. Choose the container used by your app from the list.

  3. Select Data in either the Development or Production environment.

  4. In the tab bar, click Record Types.

  5. Select a record type.

    The field names and types appear in the detail area on the right.

    ../Art/2017RecordTypes.shot/Resources/shot_2x.png../Art/2017RecordTypes.shot/Resources/shot_2x.png

    The Users record type that appears is a reserved system record type that cannot be deleted, but you can add fields to it.

Enable RecordName Indexes Before Viewing Records

All the metadata indexes for record types created using just-in-time schema are disabled by default. The recordName query index needs to be enabled to view the associated records in CloudKit Dashboard.

To enable the recordName query index

  1. In the tab bar, click Indexes and select a record type.

  2. Click Add Index and select the recordName field.

  3. Click Save Record Type.

View Records Using CloudKit Dashboard

Verify that the records you saved have all the data.

To view records

  1. In the tab bar of CloudKit Dashboard, click Records.

  2. Define your query by adding a filter or sort criteria.

  3. Click Query Records.

  4. In the second column, select a recordName.

    The record key-value pairs appear in the detail area on the right.

    ../Art/2017ViewRecord.shot/Resources/shot_2x.png../Art/2017ViewRecord.shot/Resources/shot_2x.png

Recap

In this chapter you learned how to: