Tutorial Level: Medium
This post is intended for readers who have completed a few beginner iOS tutorials. The motivation for this project is to learn and setup CloudKit completely in Swift, Apple’s new programming language. As of February 16, 2015, Apple has not released CloudKit tutorials in the new Swift language.

For additional documentation, please reference:

What’s the big fuss about CloudKit?

If you’re reading this tutorial, at some point in your life you wanted to make a mobile app. You might have thought that because mobile apps are small, they’ll be easier than a some big web project. Well, that’s generally not the case but with CloudKit, it can be. CloudKit makes app development incredibly achievable for new and old programmers. When programmers talk about front-end and back-end development, those are generally two huge and completely separate projects to tackle. CloudKit cuts the work in half by taking care of a huge chunk of the back-end development for you. CloudKit provides you with modern API’s to manage all of your data. Use CloudKit to store and retrieve your data from the cloud. You don’t have to worry about setting up a web server, authenticating users, routing http calls, securing user’s private data, etc.

CloudKit has these distinct advantages:

  • Simplicity
  • Security
  • Scalability
  • Support

CloudKit is built from the ground up by Apple, today’s technology leader. It’s only available on iOS and OSX Platforms. It scales with your application. Apple gives you ample starting storage and as your app grows in popularity, so does your available free space. If you want to be ahead of the curve, learn CloudKit now. It’s a free and powerful tool in your Apple toolbelt.

Let’s begin

Overview

In this tutorial, we’re going to build a storyboard with 2 views. I’m going to provide you with source code of 4 files. After, I’ll lightly go over iCloud dashboard, troubleshooting your iCloud sync, and how to smooth out simple CloudKit mechanics. The whole goal of this tutorial is to get you up an running with CloudKit as fast as possible.

/*
*  Copyright (c) 2015 Daniel D Peterson.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

First, create a new iOS project in XCode. I’m using 6.1.1. Let’s tackle the storyboard first. Create a simple Table View to View template.
CloudKit1

  • Table View Controller
    • Embed in Nav Controller
    • Set as Initial View Controller
    • Left bar button item: Refresh
    • Right bar button item: Add
  • View Controller
    • Embed in Nav Controller
    • Present Modally from TVC’s Add button
    • Add textfield
    • Add button below
    • Left bar button item: Cancel

Heads up: If this is too difficult for you, this tutorial is unlikely to get easier! I provided additional references at the top, use them as needed.

 

Create a new Cocoa Touch File, CKTableViewController. This view is going to display all of our CKRecords.

Don’t forget to:

  • Add cell reuse identifier to storyboard
  • Add segue identifier to storyboard
  • Hook up your refresh button

 

Add this to your ViewController. This view’s purpose is to create “activity” records.

//
//  ViewController.swift
//  iCloudHelloWorld
//
//  Created by Daniel Peterson on 2/15/15.
//  Educational purposes. Go nuts.
//

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var textField: UITextField!
    let cloud = CloudKitModel.sharedInstance()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Record Activity"
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    @IBAction func RecordButton(sender: AnyObject) {
        var text = self.textField.text
        if !text.isEmpty {
            cloud.upload(text)
            cloud.refresh()
            text = ""
            dismiss()
        }
    }
    
    func dismiss() {
        self.dismissViewControllerAnimated(true, completion: nil)
    }
    
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "unwindToMain" {
            dismiss()
        }
    }
}

Don’t forget to:

  • Unwind to TVC from VC’s Cancel bar button item
  • Add segue identifier to storyboard
  • Hook up button to storyboard
  • Hook up textfield to storyboard

 

Create a new swift file, name it Activity. We’re going to define the model of our Activity class.

//
//  Activity.swift
//  iCloudHelloWorld
//
//  Created by Daniel Peterson on 2/15/15.
//  Educational purposes. Go nuts.
//

import Foundation
import CloudKit

class Activity : NSObject{
    var record : CKRecord!
    var name : String!
    weak var database : CKDatabase!
    var date: NSDate
    init(record : CKRecord, database: CKDatabase) {
        self.record = record
        self.database = database
        self.name = record.objectForKey("Name") as String!
        self.date = record.creationDate
    }
}

 

Last but not least, create a swift file and name it CloudKitModel. This will get your toes wet with CloudKit. We’ll be executing a singleton pattern and use this class’s public methods to save and fetch records from iCloud. Giddyup.

//
//  CloudKitModel.swift
//  iCloudHelloWorld
//
//  Created by Daniel D. Peterson on 2/15/15.
//  Educational purposes. Go nuts.
//

import Foundation
import CloudKit

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

class CloudKitModel {

    var delegate : CloudKitDelegate?
    var activities = [Activity]()
    var tempRecords = [Activity]()
    
    let container : CKContainer
    let publicDB : CKDatabase
    let privateDB : CKDatabase
    
    class func sharedInstance() -> CloudKitModel {
        return cloudKitModel
    }

    init() {
        container = CKContainer.defaultContainer()
        publicDB = container.publicCloudDatabase
        privateDB = container.privateCloudDatabase
    }

    func upload(activity : NSString) {
        
        let activityRecord = CKRecord(recordType: "Activity")
        activityRecord.setValue(activity, forKey: "Name")
        activityRecord.setObject(NSDate(), forKey: "Date")

        publicDB.saveRecord(activityRecord) { record, error in
            if error != nil {
                println("error setting up record \(error)")
                return
            }
            println("saved: \(record)")
            self.addToTempRecords(record)
            self.refresh()
        }
    }

    private func addToTempRecords(rec: CKRecord?) {
        let activity = Activity(record: rec! as CKRecord, database: self.publicDB)
        self.tempRecords.insert(activity, atIndex: 0)
    }
    
    func refresh(){
        
        let query = CKQuery(recordType: "Activity", predicate: NSPredicate(value: true))
        let sort = NSSortDescriptor(key: "Date", ascending: false)
        
        query.sortDescriptors = [sort]
        
        publicDB.performQuery(query, inZoneWithID: nil) { results, error in
            if error != nil {
                dispatch_async(dispatch_get_main_queue()) {
                    self.delegate?.errorUpdating(error)
                    println("error loading: \(error)")
                }
            } else {
   
                self.activities.removeAll(keepCapacity: true)
                for record in results {
                    let activity = Activity(record: record as CKRecord, database:self.publicDB)
                    self.activities.append(activity)
                }
                dispatch_async(dispatch_get_main_queue()) {
                    self.delegate?.modelUpdated()
                    println()
                }
            }
        }
    }

    /*
    * Advanced Refresh
    * Problem with refresh, storing files in cloud is not blazing fast.
    * Recent changes should be temporarily held onto until cloud updates.
    *
    ** tempRecords
    */
    func advancedRefresh() {
        let query = CKQuery(recordType: "Activity", predicate: NSPredicate(value: true))
        publicDB.performQuery(query, inZoneWithID: nil) { results, error in
            if error != nil {
                dispatch_async(dispatch_get_main_queue()) {
                    self.delegate?.errorUpdating(error)
                    println("error loading: \(error)")
                }
            } else {
                self.activities.removeAll(keepCapacity: true)
                for record in results {
                    let activity = Activity(record: record as CKRecord, database:self.publicDB)
                    self.activities.append(activity)
                }
                if !self.tempRecords.isEmpty {
                    var index = 0
                    for rec in self.tempRecords {
                        var tempRecordExistsInResultsQuery: Bool = false
                        for record in results {
                            if record.isEqual(rec) {
                                tempRecordExistsInResultsQuery = true
                                println("temp found in results")
                            }
                        }
                        if tempRecordExistsInResultsQuery {
                            self.tempRecords.removeAtIndex(index)
                            println("Temp removed from temp")
                            index--
                        } else {
                            self.activities.append(rec)
                            println("temp added to results")
                        }
                        index++
                    }
                }
                dispatch_async(dispatch_get_main_queue()) {
                    self.delegate?.modelUpdated()
                    println()
                }
            }
        }
    }
}
let cloudKitModel = CloudKitModel()

 

Rock and roll!

Now before we try to run this bad boy, let’s do three things:

After you’ve successfully done the above steps, your app should run better than a Tesla Model S in insane mode!… OK, maybe not that good, but it should definitely run! If you’re running into any problems, leave me a comment and I’ll do my best to help you troubleshoot.

This was a fun project for me. I learned that after you successfully upload data to the cloud, it is Not instantly available. Moreover, if you use a fetch sequentially– immediately after you do a save–chances are your results will NOT include your newly saved data. Don’t fret! All we need to is temporarily hold onto recent changes until they are reflected by a fetch. I’ve included an ‘advancedRefresh’ in the CloudKitModel class that does just that. This should be a good starting ground for you to save, fetch, sort, and manipulate all your public and private data as you wish. Godspeed.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>