Cloud Kit

CloudKit, Apple’s new remote data storage service for apps based on iCloud, provides a low-cost option to store and share app data using users’ iCloud accounts as a back-end storage service.

There are two parts to CloudKit:

  1. A web dashboard to manage the record types along with any public data.
  2. A set of APIs to transfer data between iCloud and the device.

Cloud Kit is secure as well; users’ private data is completely protected, as developers can only access their own private database and aren’t able to look at any users’ private data.

CloudKit is a good option for iOS-only applications that use a lot of data but don’t require a great deal of server-side logic.

Why CloudKit?

First things first, you might wonder why you should choose CloudKit over Core Data, other commercial BaaS (Back end as a Service) offerings, or even rolling your own server.

The answers are three: simplicity, trust, and cost.

Simplicity

Unlike other backend solutions, CloudKit requires little setup. You don’t have to choose, configure, or install servers and worry about scaling and security.

Simply registering for the iOS Developer Program makes you eligible to use CloudKit – you don’t have to register for additional services or create new accounts. As part of turning on the CloudKit capabilities, all the necessary setup magic on the servers happens automatically.

There’s no need to download additional libraries and configure them – CloudKit is imported like any other iOS framework. The CloudKit framework itself also provides a level of simplicity by offering convenience APIs for common operations.

There’s also simplicity for users. Since CloudKit uses the iCloud credentials already entered when the device is set up (or through the Settings app), there’s no need for building complicated login screens. As long as they are logged in, users can seamlessly start using your app!

Trust

Another benefit to CloudKit is that users can trust the privacy and security of their data by trusting Apple, rather than app developers, since CloudKit insulates the users’ data from you.

While this lack of access can be frustrating during development (for debugging) it is a net plus since you don’t have to worry about security or convince users their data is secure. Even if an app user isn’t aware of CloudKit’s nuances, if they trust iCloud they can trust you.

Cost

Finally, for any developer the cost of running a service is a huge deal. Even the cheapest server hosts do not care if your app is free or cheap, and so it will cost even a little bit to run an app.

With CloudKit, you get a reasonable amount of storage and data transfer for public data for free. Seehttps://developer.apple.com/icloud/documentation/cloudkit-storage/ for details.

These strengths make the CloudKit service a low-hassle no-brainer for Mac and iOS apps.

Getting Started

You’ll have to set the Bundle Identifier and Team of your app before you can start coding. You need a team set in order to get the necessary entitlements from Apple, and having a unique bundle identifier makes the process a whole lot easier.

Entitlements and containers

You’ll need a container to hold the app’s records before you can add any via your app. A container is the term for the conceptual location of all the app’s data on the server—it is the grouping of public and private databases. To create a container you first need to enable the iCloud entitlements for your app.

To do so, select the Capabilities tab in the target editor, and then flip the switch in the iCloud section to ON, as shown in the screenshot below:

At this point, Xcode might prompt you to enter the Apple ID associated with your iOS developer account; if so, type it in as requested. Finally, enable CloudKit by checking the CloudKit checkbox in the Services group.

This creates a default container named iCloud.<your app’s bundle id>, as illustrated in the screenshot below:

If you see any warnings or errors when creating entitlements, when building the project, or when running the app, and it’s complaining about the container ID, here are some troubleshooting tips:

  • If there are any warnings or errors shown in Steps group in the iCloud section, try pressing the Fix Issue button. This might need to be done a few times.

  • It’s important that the app’s bundle id and iCloud containers match, and exist in the developer account. For example, if the bundle identifier is “com.<your domain>.BabiFud”, then the iCloud container name should be “iCloud.” plus the bundle bundle id: “iCloud.com.<your domain>.BabiFud”.
  • The iCloud container name must be unique because this is the global identifier used by CloudKit to access the data. Since the iCloud container name contains the bundle id, this means that the bundle id must also be unique (which is why it has to be changed from com.raywendrelich.BabiFud).
  • In order for the entitlements piece to work, the app/bundle id has to be listed in the App IDs portion of Certificates, Identifiers, and Profiles portal. This means that that the certificate used to sign the app has to be from the set team id, and has to list the app id, which also implies the iCloud container id.

    Normally Xcode does all of this automatically for you, if you are signed in to a valid developer account. Unfortunately sometimes this gets out of sync. It can help to start with a fresh ID, and then change the CloudKit container ID to match, using the iCloud capabilities pane. Otherwise, to fix it you may have to edit the info.plist or in BabiFud.entitlements files to make sure the id values there reflect what you set for the bundle id.

Introducing the CloudKit Dashboard

After setting up the necessary entitlements, the next step when implementing CloudKit is to create some record types that define the data used by your app, and you can do this using the CloudKit dashboard. Click CloudKit Dashboard, found in the target’s Capabilities pane, under iCloud, as shown below:

You can also launch the CloudKit dashboard by opening the URL https://icloud.developer.apple.com/dashboard/ in your browser.

Here’s a basic overview of what you’ll see in the CloudKit dashboard, for the purposes of this tutorial:

The SCHEMA section of the left-hand pane represents the high level objects of a CloudKit container: Record TypesSecurity Roles, and Subscription Types.

Record Type is a set of attributes that define individual records. In terms of object orientated programming, a Record Type is like a class template for individual objects. A record can be considered an instance of a particular Record Type. It represents structured data in the container, much like a typical row in a database, and encapsulates a series of key/value pairs.

The PUBLIC DATA and PRIVATE DATA sections let you add data to, or search for data, in the databases you have access to; remember, as a developer you access all public data, but only your own private data. 

The User Records store data about the current iCloud user such as name and email. A Record Zone (here noted as the Default Zone) is used to provide a logical organization to a private database, by grouping records together. Custom zones support atomic transactions by allowing multiple records to be saved at the same time before processing other operations. 

The ADMIN section provides a means to configure the dashboard permissions available to your team members. If you have multiple people on your development team, you can restrict their ability to edit data here. This too is out-of-scope for this book.

Adding the Establishment Record Type

With Record Types selected, click the icon in the top left of the detail pane to add a new record type, as shown below:

Name your new record type Post.

Think about the design of your app for a moment. Each establishment you’ll want to track has lots of data: a name, a location, and whether or not various child-friendly options are available. Record types use attributes to define the various pieces of data contained in each record.

You’ll see a row of fields where you can define the NameAttribute Type, and Index, as shown below. A template StringField has been automatically created for you.

So far only String field type is supported.

For testing purposes we will create one filed for each of the supported data type. List of attributes should look like the following:

 

Click Save at the bottom of the page to save your new record type.

You’re now ready to add some sample establishment records to your database.

Select Default Zone under the PUBLIC DATA section in the navigation pane on the left; this zone will contain the public records for your app. Select the Post record type from the drop-down list in the center pane if it’s not already selected, then click the + icon in the right detail pane, as shown in the screenshot below:

This will create a new, empty Post record. At this point you’re ready to enter some test data for your app. The following sample establishment data is fictional; the data has them located near Apple’s headquarters so they’re easy to find in the simulator.

The article was originally made by Michael Katz. And updated by Stan's Assets team to match Unity C# Examples.

Querying Establishment Records

Now it's time to start scripting. The IOS Native CloudKit scripting API allows you to Create, Delete and Fetch Cloud Kit records. In case you will need more Cloud Kit functionality you can always send us the feature request to the support@stansassets.com. Code snippets for the common operations with the Cloud Kit API can be found bellow.

 How to Create a new record

CK_RecordID recordId =  new CK_RecordID("1");

CK_Record record =  new CK_Record(recordId, "Post");
record.SetObject("PostText", "Sample point of interest");
record.SetObject("PostTitle", "My favorite point of interest");


CK_Database database = ISN_CloudKit.Instance.PublicDB;
database.SaveRecrod(record);
database.ActionRecordSaved += Database_ActionRecordSaved;


void Database_ActionRecordSaved (CK_RecordResult res) {
	res.Database.ActionRecordSaved -= Database_ActionRecordSaved;

	if(res.IsSucceeded) {
		Debug.Log("Database_ActionRecordSaved:");
		Debug.Log("Post Title: "  + res.Record.GetObject("PostTitle"));
	} else {
		Debug.Log("Database_ActionRecordSaved, Error: " + res.Error.Description);
	}
}

 How to Delete a record

CK_RecordID recordId =  new CK_RecordID("1");
CK_Database database = ISN_CloudKit.Instance.PublicDB;

database.DeleteRecordWithID(recordId);
database.ActionRecordDeleted += Database_ActionRecordDeleted;


void Database_ActionRecordDeleted (CK_RecordDeleteResult res) {
	res.Database.ActionRecordDeleted -= Database_ActionRecordDeleted;

	if(res.IsSucceeded) {
		Debug.Log("Database_ActionRecordDeleted, Success: ");
	} else {
		Debug.Log("Database_ActionRecordDeleted, Error: " + res.Error.Description);
	}
}

How to Fetch a record

CK_RecordID recordId =  new CK_RecordID("1");
CK_Database database = ISN_CloudKit.Instance.PublicDB;

database.FetchRecordWithID(recordId);
database.ActionRecordFetchComplete += Database_ActionRecordFetchComplete;

void Database_ActionRecordFetchComplete (CK_RecordResult res) {
	res.Database.ActionRecordFetchComplete -= Database_ActionRecordFetchComplete;

	if(res.IsSucceeded) {
		Debug.Log("Database_ActionRecordFetchComplete:");
		Debug.Log("Post Title: "  + res.Record.GetObject("PostTitle"));
	} else {
        Debug.Log("Database_ActionRecordFetchComplete, Error: " + res.Error.Code);
		Debug.Log("Database_ActionRecordFetchComplete, Error: " + res.Error.Description);
	}
}

How to modify already created record.

First of all, you need to fetch this record from the cloud. If record was fetched successfully, you can ready / change record data. 

CK_RecordID recordId =  new CK_RecordID("1");
CK_Database database = ISN_CloudKit.Instance.PublicDB;


//Fetching old recrod from PublicDB
database.FetchRecordWithID(recordId);
database.ActionRecordFetchComplete += Database_ActionRecordFetchComplete;

void Database_ActionRecordFetchComplete (CK_RecordResult res) {
	res.Database.ActionRecordFetchComplete -= Database_ActionRecordFetchComplete;

	if(res.IsSucceeded) {

		CK_Record record = res.Record;
		//Retriving Old Value
		string Title = record.GetObject("PostTitle");

		//Setting New Value
		record.SetObject("PostTitle", "NEW TITLE");

		//Saving a record
		database.SaveRecrod(record);
	
	} else {
		Debug.Log("Database_ActionRecordFetchComplete, Error: " + res.Error.Description);
	}
}

 

 

API Reference

ISN_CloudKit

public class ISN_CloudKit : ISN_Singleton<ISN_CloudKit>  {

	/// <summary>
	/// The database containing the user’s private data.
	/// 
	/// The database in this property is available only if the device has an active iCloud account. 
	/// Access to the database is limited to the user of that iCloud account by default. 
	/// The current user owns all content in the private database and is allowed to read and write that content. 
	/// Data in the private database is not visible in the developer portal or to any other users.
	/// 
	/// Data stored in the private database counts against the storage quota of the current user’s iCloud account.
	/// 
	/// If there is no active iCloud account on the user’s device, this property still returns a valid database object, 
	/// but attempts to use that object will return errors. 
	/// </summary>
	public CK_Database PrivateDB {get;}
	

	/// <summary>
	/// The database containing the data shared by all users.
	/// 
	/// The database in this property is available regardless of whether the user’s device has an active iCloud account. 
	/// The contents of the public database are readable by all users of the app, and users 
	/// have write access to the records (and other data objects) they create. 
	/// Data in the public database is also visible in the developer portal, 
	/// where you can assign roles to users and restrict access as needed.
	/// 
	/// Data stored in the public database counts against your app’s iCloud storage 
	/// quota and not against the quota of any single user.
	/// </summary>
	public CK_Database PublicDB {get;}

}

CK_Database

public class CK_Database {

	/// <summary>
	/// Saves one record asynchronously to the current database.
	/// 
	/// This method saves the record only if it has never been saved before or if it is newer than the version on the server. 
	/// You cannot use this method to overwrite newer versions of a record on the server.
	/// </summary>
	/// <param name="record">The record object you attempted to save.</param>
	public void SaveRecrod(CK_Record record);


	/// <summary>
	/// Fetches one record asynchronously from the current database.
	/// 
	/// Use this method to fetch records that are not urgent to your app’s execution. 
	/// This method fetches the record with a low priority, which may cause the task to execute after higher-priority tasks.
	/// </summary>
	/// <param name="recordId">The ID of the record you want to fetch.</param>
	public void FetchRecordWithID(CK_RecordID recordId);


	/// <summary>
	/// Deletes the specified record asynchronously from the current database.
	/// 
	/// Deleting a record may trigger additional deletions if the record was referenced by other records. 
	/// This method reports only the ID of the record you asked to delete. 
	/// CloudKit does not report deletions triggered by owning relationships between records.
	/// </summary>
	/// <param name="recordId">The ID of the record you want to delete.</param>
	public void DeleteRecordWithID(CK_RecordID recordId);


	/// <summary>
	/// Searches the specified zone asynchronously for records that match the query parameters.
	///
	/// Do not use this method when the number of returned records is potentially more than a few hundred records. 
	/// For efficiency, all queries automatically limit the number of returned records based on current conditions. 
	/// If your query hits the maximum value, this method returns only the first portion of the overall results. 
	/// The number of returned records should be sufficient in most cases.
	/// </summary>
	/// <param name="query">The ID of the record you want to delete.</param>
	public void PerformQuery(CK_Query query);

}

CK_Record

public class CK_Record  {

	/// <summary>
	/// Initializes and returns a record using an id that you provide..
	/// 
	/// Use this method to initialize a new record object with the specified ID. The newly created record contains no data.
	/// </summary>
	/// <param name="id">The ID to assign to the record itself. The ID cannot currently be in use by any other record and must not be nil. </param>
	/// <param name="type">A string reflecting the type of record that you want to create. Define the record types that your app supports, and use them to distinguish between records with different types of data. </param>
	public CK_Record(CK_RecordID id, string type);


	public void SetObject(string key, string value);
	public string GetObject(string key);


	public CK_RecordID Id {get;}
	public string Type {get;}
}

CK_RecordID

public class CK_RecordID  {
	/// <summary>
	/// Initializes and returns a new record ID with the specified name in the default zone.
	/// 
	/// Use this method when you are creating or searching for records in the default zone.
	/// </summary>
	/// <param name="recordName">The name to use to identify the record. The string must contain only ASCII characters and must not exceed 255 characters. If you specify nil or an empty string for this parameter, this method throws an exception.</param>
	public CK_RecordID(string recordName);


	public string Name {get;}
}

CK_RecordResult

public class CK_RecordResult : ISN_Result {

	public CK_Record Record {get;}
	public CK_Database Database {get;}
}

CK_RecordDeleteResult

public class CK_RecordDeleteResult : ISN_Result {

	public CK_RecordID RecordID {get;}
	public CK_Database Database {get;}
}