CloudKit Record Zone

When you use the CloudKit Dashboard or Swift code to add records in the CloudKit private or public database, CloudKit put them in the private database’s Default Zone or the public database’s Default Zone.

cloudkit-defaultzone-private-db cloudkit-defaultzone-public-db
Private database’s Default Zone Public database’s Default Zone

Now, a zone is like a table in the CloudKit database and it act like a silo for records, letting you separate different types of data in the database. You use the CloudKit Dashboard Swift code to create additional zones only in the private database. If your data structure is simple, you can add records in the private and public database’s Default Zone.

In this tutorial you learn how to do the following tasks in the CloudKit’s private database:

  • Create a custom zone
  • Add records in the custom zone
  • Display the custom zone records
  • Edit a single record in the custom zone
  • Delete a record from the custom zone
  • Delete all records in the custom zone

Now, click the MainViewController.swift file to load it in the standard code editor, because you’ll be adding code in the file to implement above tasks.

caution Code presented on this page assume you are using Xcode 6.4 and Swift version 1.2. So if you are using a newer version of Swift, it may produce errors. Fix them by using Xcode’s Fix-it tool. I assume you are a registered member of Apple’s iOS Developer Program, and you have a real iPhone/iPad device to test the CloudKit code presented here.

Create a custom zone

As you already know, you can only create additional zones only in the CloudKit’s private database. Let’s create one called “friendsZone” in the CloudKit’s private database. You do that by replacing the executeCodeButtonClicked() function’s code with this one:

// Put the CloudKit private database in a constants
let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase

// Create zone called, friendsZone
let customZone = CKRecordZone(zoneName: "FriendsZone")

// Save the friendsZone in the private database
privateDatabase.saveRecordZone(customZone, completionHandler: ({returnRecord, error in
  if error != nil {
    // Zone creation failed
    NSOperationQueue.mainQueue().addOperationWithBlock {
      self.textView.text = "Cloud Error\n\(error.localizedDescription)"
    }
  } else {
    // Zone creation succeeded
    NSOperationQueue.mainQueue().addOperationWithBlock {
      self.textView.text = "The 'FriendsZone' was successfully created in the private database."
    }
  }
}))

Now, run the app in the iPhone 6 Simulator. Figure 1 below shows output you’ll see on the simulator’s screen, when you click the “Execute Code” button. Figure 2 shows what the friendsZone you created in the CloudKit’s private database look like.

cloudkit-figure5-1 cloudkit-figure5-2a
Figure 1 Figure 2

Add records in the custom zone

Here is the code to add a single record in the custom zone you just created in the CloudKit’s private database.

@IBAction func executeCodeButtonClicked() {
  // Put the CloudKit private database in a constants
  let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase

  // Create a zone name
  let customZone = CKRecordZone(zoneName: "FriendsZone")

  // Create a friendRecord
  var friendRecord = CKRecord(recordType: "Friends", zoneID: customZone.zoneID)

  // Add two fields in the friendRecord
  friendRecord.setObject("Barbara", forKey: "firstName")
  friendRecord.setObject("Walter", forKey: "lastName")

  // Save the friendRecord in the private database's friendsZone
  privateDatabase.saveRecord(friendRecord, completionHandler: { returnRecord, error in
    if error != nil {
      // On the main thread display the error in the textView
      NSOperationQueue.mainQueue().addOperationWithBlock {
        self.textView.text = "Cloud Error\n\(error.localizedDescription)"
      }
    } else {
      // On the main thread display a success message in the textView
      NSOperationQueue.mainQueue().addOperationWithBlock {
        self.textView.text = "Record saved successfully in the custom zone called, FriendsZone."
      }
    }
  })
}

Run the app in the iPhone 6 Simulator and click the Execute Code button. You will see output shown in Figure 3 on the simulator’s screen.

cloudkit-figure5-2
Figure 3

To verify that code you entered in the executeCodeButtonClicked() function worked, go to the CloudKit Dashboard and click the “FriendsZoneode” item in the PRIVATE DATA section. The CloudKit Dashboard will look like this:

Figure 4
Figure 4

To make above message go away so you can see the record you entered in the FriendsZone click the “Add ID Query Index” link.

Figure 5
Figure 5

Now, add a second record in the custom zone by changing code line 12 and 13 to this. Run the app in the iPhone 6 Simulator and click the Execute Code button.

friendRecord.setObject("Hayden", forKey: "firstName")
friendRecord.setObject("Clark", forKey: "lastName")

Repeat above steps to add a third record in the custom zone; however, change code line 12 and 13 to this:

friendRecord.setObject("Ralph", forKey: "firstName")
friendRecord.setObject("Furgerson", forKey: "lastName")

Now, go to the CloudKit Dashboard and click the “FriendsZone” item in the first column to see all three records you’ve added in the private database’s custom zone.

cloudkit-figure5-4
Figure 6

Display the custom zone records

To display records you entered in the private database’s custom zone, you’ll have to add code shown below in the executeCodeButtonClicked() function.

@IBAction func executeCodeButtonClicked() {
    // Put the CloudKit private database in a constants
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase

    // Create a zone name
    let customZone = CKRecordZone(zoneName: "FriendsZone")

    // Setup a predicate and a query
    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: "Friends", predicate: predicate)

    // Execute the query which fetch all records from the private database's custome zone
    privateDatabase.performQuery(query, inZoneWithID: customZone.zoneID) {
        records, error in
        if error != nil {
            // The query returned an error, on the main thread, show it in the textView
            NSOperationQueue.mainQueue().addOperationWithBlock {
                self.textView.text = error.localizedDescription
            }
        } else {
            // The query return one or more records, declare an array variable for holding
            // records fetched from the private database's FriendsZone
            var names = [String]()

            // Add each record in the names array
            for record in records {
                let fName = record.valueForKey("firstName") as! String
                let lName = record.valueForKey("lastName") as! String
                names.append(self.emojiBullet + fName + " " + lName)
            }

            // Convert the names array to a string
            let stringRepresentation = "\n".join(names)

            // On the main thread dump content of the stringRepresentation variabl in the textView
            NSOperationQueue.mainQueue().addOperationWithBlock {
                self.textView.text = stringRepresentation
            }
        }
    }
}

Above code pretty much fetch all records from the private database’s custom zone and display them in the textView, like this:

cloudkit-figure5-5
Figure 7

Edit a single record in the custom zone

Say there are 10 records in the custom zone and you want to edit the one highlighted in Figure 8.

cloudkit-figure5-6a
Figure 8

You will have to enter code in the executeCodeButtonClicked() function to perform these tasks:

  1. Fetch the record you want to edit from the private database’ custom zone.
  2. Edit the fetched record’s fields.
  3. Save the edited record back in the private database’s custom zone.

Now, here’s the code to implement the first task:

@IBAction func executeCodeButtonClicked() {
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
    var recordToEdit: CKRecord!
    let customZone = CKRecordZone(zoneName: "FriendsZone")
    let lastName = "Wenderlich"

    let predicate = NSPredicate(format: "lastName = %@", lastName)
    let query = CKQuery(recordType: "Friends", predicate: predicate)

    privateDatabase.performQuery(query, inZoneWithID: customZone.zoneID) {
        queryResults, error in
        if (error != nil) {
            NSOperationQueue.mainQueue().addOperationWithBlock {
                self.textView.text = error.localizedDescription
            }
        } else {
            if queryResults.count > 0 {
                recordToEdit = queryResults[0] as! CKRecord
                let dbFirstName = recordToEdit.valueForKey("firstName") as! String
                let dbLastname = recordToEdit.valueForKey("lastName") as! String
                // DEBUG CODE
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    self.textView.text = "The query returned \(queryResults.count) record:\n"
                    + dbFirstName + " " + dbLastname
                }
            } else {
                 NSOperationQueue.mainQueue().addOperationWithBlock {
                    self.textView.text = "Sorry, no record exists in the database for the last name, \(lastName)."
                }
            }
        }
    }
}

Now, when you run the app in the iPhone 6 Simulator and click the Execute Code button. You will see output shown in the image below, on the simulator’s screen. As you can see; the the “DEBUG CODE” displayed the fetched record’s fields in the textView control, thus letting you know that the CloudKit’s performQuery() function did indeed returned a record for the last name you entered in the lastName variable.

cloudkit-figure5-6b
Figure 9
Code Analysis

Let us take some time now to examine the code. The first set of statements setup three constants and a variable. The second set of statements setup a predicate and a query. The final set of statements called the CloudKi’s performQuery() function on the privateDatabase. The function’s job is to query the private database’s custom zone for records that match the last name stored in the lastName constant. If the performQuery() function failed to do its job; it returns an error message and we store it in the error variable. If the function found records that match the lastName, it returns an array of records and we store them in the queryResults array. Notice how the textView was updated on the main thread-see code line 72, 83, and 88.

Now, here is the code to implement the second and third task.

@IBAction func executeCodeButtonClicked() {
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
    var recordToEdit: CKRecord!
    let customZone = CKRecordZone(zoneName: "FriendsZone")
    let lastName = "Wenderlich"

    let predicate = NSPredicate(format: "lastName = %@", lastName)
    let query = CKQuery(recordType: "Friends", predicate: predicate)

    //Task 1: FETCH THE RECORD FROM THE PRIVATE DATABASE
    privateDatabase.performQuery(query, inZoneWithID: customZone.zoneID) {
        queryResults, error in
        if (error != nil) {
            NSOperationQueue.mainQueue().addOperationWithBlock {
                self.textView.text = error.localizedDescription
            }
        } else {
            if queryResults.count > 0 {
                recordToEdit = queryResults[0] as! CKRecord
                //Task 2: UPDATE THE FETCHED RECORD'S FIELDS
                recordToEdit.setObject("Fiona", forKey: "firstName")
                recordToEdit.setObject("Edwards", forKey: "lastName")

                //Task 3: SAVE THE EDITED RECORD BACK IN THE PRIVATE DATABASE
                privateDatabase.saveRecord(recordToEdit) {
                    returnedRecord, error in
                    if error != nil {
                        NSOperationQueue.mainQueue().addOperationWithBlock {
                            self.textView.text = "Record not updated:\n\(error.localizedDescription)"
                        }
                    } else {
                        NSOperationQueue.mainQueue().addOperationWithBlock {
                            self.textView.text = "Record updated."
                        }
                    }
                }
            } else {
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    self.textView.text = "Sorry, record not found."
                }
            }
        }
    }
}

Run the app in the iPhone 6 Simulator and click the “Execute Code” button. Figure 10 below show output you’ll see on the simulator’s screen. Figure 111 shows what the updated record look like in the CloudKit Dashboard.

cloudkit-figure5-6d
Figure 10: iPhone Simulator Figure 11: CloudKit Dashboard

Delete a record from the custom zone

Say you wanted to delete the fourth record from the private database’s custom zone.

Figure 11
Figure 12

You’ll have to enter code in the executeCodeButtonClicked() function to perform these tasks:

  1. Fetch the record you want to delete from the private database’s custom zone
  2. Delete the fetched record from the private database’s custom zone.
  3. On the main thread, update the textView.

Now, here’s the code to implement above tasks in the executeCodeButtonClicked() function.

@IBAction func executeCodeButtonClicked() {
    // Put the CloudKit private database in a constants
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase

    // Create a custom zone name
    let customZone = CKRecordZone(zoneName: "FriendsZone")
    let lastName = "Furgerson"

    // Setup a predicate and the query that'll fetch the record the user want to delete from the private database's custom zone
    let predicate = NSPredicate(format: "(lastName == %@)", lastName)
    let query = CKQuery(recordType: "Friends", predicate: predicate)

    // Execute the query which fetch one or more records from private database's custom zone
    privateDatabase.performQuery(query, inZoneWithID: customZone.zoneID) {
        records, error in
        if error != nil {
            // The query returned an error, on the main thread, show it in the textView
            NSOperationQueue.mainQueue().addOperationWithBlock {
                self.textView.text = error.localizedDescription
            }
        } else {
            // The query return one or more records
            if records.count > 0 {
                // Put the first record in a constant
                let recordToDelete = records.first as! CKRecord

                // Delete the record from the private database's custom zone
                privateDatabase.deleteRecordWithID(recordToDelete.recordID) {
                results, error in
                    if error != nil {
                        // Record deletion failed. On the main thread, update the textView
                        NSOperationQueue.mainQueue().addOperationWithBlock {
                            self.textView.text = "Record Deletion Error:\n\(error.localizedDescription)"
                        }
                    } else {
                        // Record deletion was successful. On the main thread, update the textView
                        NSOperationQueue.mainQueue().addOperationWithBlock {
                            self.textView.text = "The record was deleted from the private database."
                        }
                    }
                }
            }
        }
    }
}

When you finish entering above code in the executeCodeButtonClicked() function, run the app in the iPhone Simulator, and click the Execute Code button. The image below show output you’ll see on the simulator’s screen and the CloudKit Dashboard.

cloudkit-figure5-8
Figure 13

Delete all records in the custom zone

Currently there are nine records in the private database’s custom zone. To delete all of them, you’ll have to entered code in the ExecuteCodeButtonClicked() function to perform these tasks:

  1. Show the user an alert view, prompting her if she want to delete all records from the private database’s custom zone.
  2. If the user click the Yes button on the alert view, delete all records from the custom zone, and remove all records from the friendsList array.
  3. If the user click the No button on the alert view, do nothing.

All righty then, here is the code to implement above tasks.

@IBAction func executeCodeButtonClicked() {
    let controller = UIAlertController(title: "Warning!",
        message:"Are you sure you want to permanently delete all friends records from the database?",
        preferredStyle: .Alert)

    controller.addAction(UIAlertAction(title: "Yes", style: .Default, handler: {
        (action: UIAlertAction!) in
        self.deleteAllCustomZoneRecords()
        self.friendsList.removeAll(keepCapacity: false)
    }))

    controller.addAction(UIAlertAction(title: "No", style: .Default, handler: nil))
    presentViewController(controller, animated: true, completion: nil)
}

Above code create and display an alert view with two buttons. On the iPhone 6 Simulator, it look like this:

cloudkit-figure5-9
Figure 14

On code line 06, we called a user defined function that delete all records from the private database’s custom zone. Here is the code to implement it in the MainViewController.swift file.

func deleteAllCustomZoneRecords() {
    // Put the CloudKit private database in a constants
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase

    // Create a custom zone name
    let customZone = CKRecordZone(zoneName: "FriendsZone")

    // Setup a predicate and query that'll fetch all records from the private database's custom zone
    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: "Friends", predicate: predicate)

    // Execute the query
    privateDatabase.performQuery(query, inZoneWithID: customZone.zoneID) {
        allRecords, error in
        if error != nil {
            // The query returned an error. On the main thread show it in the textView
            NSOperationQueue.mainQueue().addOperationWithBlock {
                self.textView.text = "Cloud error\n\(error.localizedDescription)"
            }
        } else {
            // The query return one or more records
            if allRecords.count > 0 {
                // Delete all of them from the private database's custom zone
                for recordToDelete in allRecords {
                    privateDatabase.deleteRecordWithID(recordToDelete.recordID, completionHandler: {
                        record, error in
                        if error != nil {
                            // Record deletion failed; on the main thread, update the textView
                            NSOperationQueue.mainQueue().addOperationWithBlock {
                                self.textView.text = "Record deletion error\n\(error.localizedDescription)"
                            }
                        } else {
                            // Record deletion was successful; on the main thread, update the textView
                            NSOperationQueue.mainQueue().addOperationWithBlock {
                                self.textView.text = "Records deleted successfully."
                            }
                        }
                    })
                }
            }
        }
    }
}

Run the app on the iPhone 6 Simulator and click the “Execute Code” button. You will see the alert view on the sim’s screen-see Figure 12 above. Click the “No” button and the alert view will disappear. Now, check the custom zone in the CloudKit Dashboard and you’ll see that the records are still there. Run the app again on the iPhone 6 Simulator and click the “Execute Code” button. You will see the alert view again. This time click the “Yes” button. The Alert View will disappear and you will the message shown in Figure 15 on the sim’s screen.

cloudkit-figure5-10
Figure 15

Go to the CloudKit Dashboard to verify that the deleteAllCustomZoneRecords() function deleted all records from the private database’s custom zone. As you can see in the image below, all records were deleted from the private database’s FriendsZone.

cloudkit-figure5-11
Figure 16