This is the part 2 of the iOS 8 CloudKit tutorial series. If you haven’t read the part 1 I suggest you to take a look at Part1 before you move on to the next section

What we will cover

UI for ListViewController

NSPredicate

NSSortDescriptors

CKQuery

Protocol and Delegate/Delegation iOS (Our example CloudKitDelegate)

CKModifyRecordsOperation

Final Working Fetching Code

UI for ListViewController

This is how our story board looked at the end of part 1. For more information on layouts checkout these videos iOS Adaptive Layouts and iOS Autolayouts



Lets go to object library and drag a navigation controller from the library onto the main storyboard.



Select the view controller which was already existing. Goto Editor embed in navigation controller.



CTRL-DRAG from Root View Controller to the just embedded navigation view controller and select present modally.

Add bar buttons for Cancel and Done . Finally setup the IBActions and IBOutlets.

NSPredicate

NSPredicate is basically the matching criteria like id should be "123" or id should be "123" and count > 5

predicate can’t be nil if you want to fetch all records use the below mentioned predicate let predicate = NSPredicate(value: true)

NSSortDescriptors

NSSortDescriptors defined the order in which the records are retrieved from the iCloud using cloudKit.

Its take the key on which the sorting is supposed to be done along with the ordering like ascending:true or ascending:false . If there are more than one sort descriptors they can be passed as an array.

If you are interested in learning iOS Development I suggest you take a look at Building an app every month in 2015

CKQuery

CKQuery is analogous to select query in RDBMS world. The query consists of 2 or more parts.

RecordType : What type of object to search for. In our example it is Todos but for other applications it can be a Post, Message etc.

Predicate : Predicates are the condition on which the records should be matched against

SortDescriptors : The order in which the keys should be returned. We provide the key and the order like ascending or descending.

Example of the above scenario

let predicate = NSPredicate(value: true) let sort = NSSortDescriptor(key: "creationDate", ascending: false) let query = CKQuery(recordType: "Todos", predicate: predicate) query.sortDescriptors = [sort]

Protocol and Delegate/Delegation iOS

Protocols : are similar to interfaces in OOP world. Delegate : “A delegate is an object that acts on behalf of, or in coordination with, another object when that object encounters an event in a program.”

In CloudKit most of the operations are async, hence are very good candidates for the Delegation pattern. Delegating object will send a message or call a callback when certain events are completed. Its the responsibility of the delegate object to implement those protocols and handle callbacks generated by the delegating object.

protocol CloudKitDelegate { func errorUpdating(error: NSError) func modelUpdated() }

In our case our viewcontroller will handle this protocol and take appropriate actions when CloudKit events are triggered

CKModifyOperation

I faced a wierd issue when I tried to display the todo entries after adding to iCloud. I was not able to fetch the last entry added. Documentation mentioned that all the operations are async and are run on the low priority threads and if I need to save something immediately I need to use CKModifyOperation. I tried but unfortunately didn’t work for me. If someone finds a solution to this please let me know in the comments.

This method saves the record with a low priority, which may cause the task to execute after higher-priority tasks. To save records more urgently, create a CKModifyRecordsOperation object with the desired priority. You can also use that operation object to save multiple records simultaneously.

Problem

CloudKit not returning the most recent data

let todoRecord = CKRecord(recordType: "Todos") todoRecord.setValue(todo, forKey: "todotext") publicDB.saveRecord(todoRecord, completionHandler: { (record, error) -> Void in NSLog("Saved in cloudkit") let predicate = NSPredicate(value: true) let query = CKQuery(recordType: "Todos", predicate: predicate) self.publicDB.performQuery(query, inZoneWithID: nil) { results, error in if error != nil { dispatch_async(dispatch_get_main_queue()) { self.delegate?.errorUpdating(error) return } } else { NSLog("###### fetch after save : \(results.count)") dispatch_async(dispatch_get_main_queue()) { self.delegate?.modelUpdated() return } } } Result Before saving in cloud kit : 3 Saved in cloudkit ###### Count after save : 3

WorkAround

Add it to the todos array on the client side.

What I tried

let ops = CKModifyRecordsOperation(recordsToSave: [todoRecord], recordIDsToDelete: nil) ops.savePolicy = CKRecordSavePolicy.AllKeys ops.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in NSLog("Completed Save to cloud") let predicate = NSPredicate(value: true) let query = CKQuery(recordType: "Todos", predicate: predicate) self.publicDB.performQuery(query, inZoneWithID: nil) { results, error in if error != nil { dispatch_async(dispatch_get_main_queue()) { self.delegate?.errorUpdating(error) return } } else { self.todos.removeAll() for record in results{ let todo = Todos(record: record as CKRecord, database: self.publicDB) self.todos.append(todo) } NSLog("fetch after save : \(self.todos.count)") dispatch_async(dispatch_get_main_queue()) { self.delegate?.modelUpdated() return } } } } publicDB.addOperation(ops)

Final Working Fetching Code

import Foundation import CloudKit protocol CloudKitDelegate { func errorUpdating(error: NSError) func modelUpdated() } class CloudKitHelper { var container : CKContainer var publicDB : CKDatabase let privateDB : CKDatabase var delegate : CloudKitDelegate? var todos = [Todos]() class func sharedInstance() -> CloudKitHelper { return cloudKitHelper } init() { container = CKContainer.defaultContainer() publicDB = container.publicCloudDatabase privateDB = container.privateCloudDatabase } func saveRecord(todo : NSString) { let todoRecord = CKRecord(recordType: "Todos") todoRecord.setValue(todo, forKey: "todotext") publicDB.saveRecord(todoRecord, completionHandler: { (record, error) -> Void in NSLog("Before saving in cloud kit : \(self.todos.count)") NSLog("Saved in cloudkit") self.fetchTodos(record) }) } func fetchTodos(insertedRecord: CKRecord?) { let predicate = NSPredicate(value: true) let sort = NSSortDescriptor(key: "creationDate", ascending: false) let query = CKQuery(recordType: "Todos", predicate: predicate) query.sortDescriptors = [sort] publicDB.performQuery(query, inZoneWithID: nil) { results, error in if error != nil { dispatch_async(dispatch_get_main_queue()) { self.delegate?.errorUpdating(error) return } } else { self.todos.removeAll() for record in results{ let todo = Todos(record: record as CKRecord, database: self.publicDB) self.todos.append(todo) } if let tmp = insertedRecord { let todo = Todos(record: insertedRecord! as CKRecord, database: self.publicDB) /* Work around at the latest entry at index 0 */ self.todos.insert(todo, atIndex: 0) } NSLog("fetch after save : \(self.todos.count)") dispatch_async(dispatch_get_main_queue()) { self.delegate?.modelUpdated() return } } } } } let cloudKitHelper = CloudKitHelper()

If you have any questions/comments do comment on the post.

Github Repo : CloudKit