Active Filters: CloudKit

Topics

#67: CloudKit Assets ⛅️

Topics

One of the best parts of CloudKit is how great it is at handling not just our models, but also larger assets like images, audio, or video.

Assets are saved just like any other property. Here we'll attach an image captured from the user's camera to a new record. Then we'll upload it to CloudKit using a CKModifyRecordsOperation (covered in more detail in Bite #31). In our case we're only saving a single record, but we're using an operation anyway, so we can take advantage of its perRecordProgressBlock, and track the upload progress of our asset.

func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
  guard let mediaURL = info[UIImagePickerControllerMediaURL] as? NSURL else { return }

  let spaceshipRecord = CKRecord(recordType: "Spaceship")

  spaceshipRecord["model"] = "Tantive IV"
  spaceshipRecord["maxSpeed"] = 950 // in km
  spaceshipRecord["image"] = CKAsset(fileURL: mediaURL)

  let operation = CKModifyRecordsOperation(recordsToSave: [spaceshipRecord], recordIDsToDelete: nil)

  operation.perRecordProgressBlock = { self.progressView.progress = $1 }
  operation.completionBlock = { self.progressView.hidden = true }

  progressView.hidden = false

  CKContainer.defaultContainer().publicCloudDatabase.addOperation(operation)
}

It's worth noting that CloudKit doesn't seem to report progress constantly as we might expect. It seems to report between 0-3 times depending on the size of the asset we're uploading.

After fetching a record containing an asset, we can grab the downloaded file from disk using the fileURL property of the CKAsset:

let asset = spaceshipRecord["image"] as! CKAsset

imageView.image = UIImage(
  contentsOfFile: asset.fileURL.absoluteString
)

Topics

#31: CloudKit Operations ⛅️🏥

Topics

Diving deeper into CloudKit today we'll look at some more advanced ways of interacting with your app's data using operations. CloudKit's NSOperation-based API allows for things like batch saves/changes, ‘paging' through data and more.

Here's a few of the things you can do:

Advanced Querying

let predicate = NSPredicate(value: true)
let query = CKQuery(
  recordType: "Spaceship", 
  predicate: predicate
)

let operation = CKQueryOperation(query: query)

operation.desiredKeys = ["topSpeed", "name"]
operation.resultsLimit = 30

let container = CKContainer.defaultContainer()
let publicDB = container.publicCloudDatabase

publicDB.addOperation(operation)

Batch Saves/Changes

Here I'm using CKModifyRecordsOperation to seed my app with example data during development.

var objects = [Spaceship]()

for i in 0...100 {
  objects.append(Spaceship(
    name: "Test Spaceship"
  ))
}

let operation = CKModifyRecordsOperation(
  recordsToSave: objects.map { $0.toRecord() },
  recordIDsToDelete: nil
)

publicDB.addOperation(operation)

Paging

CKQueryOperation‘s queryCompletionBlock provides an optional CKQueryCursor. You can store this and supply it to your next query to load the next logical 'page' of records.

Progress

One of the great things about CKModifyRecordsOperation and CKQueryOperation is their ability to report progress at a per-record level.

Topics

#28: Discovering Users in CloudKit 👤⛅️

Topics

We continue our tour through CloudKit today with a look at CKDiscoveredUserInfo and it's associated functions. CKDiscoveredUserInfo actually pop up in two different use cases: Grabbing the user's first and last name as well as letting them discover their friends who are also using your app. Let's get started, first we request permission:

let container = CKContainer.defaultContainer()

container.requestApplicationPermission(.PermissionUserDiscoverability) { (status, error) in
    guard error == nil else { return }

    if status == CKApplicationPermissionStatus.Granted {
      // yay!
    }
}

Then we fetch the current user's CKRecord, and fetch their name:

container.fetchUserRecordIDWithCompletionHandler { (recordID, error) in
  guard error == nil else { return }
  guard let recordID = recordID else { return }

  container.discoverUserInfoWithUserRecordID(recordID) { (info, fetchError) in
    // use info.firstName and info.lastName however you need
  }
}

All that's left is to let the user discover their friends:

container.discoverAllContactUserInfosWithCompletionHandler { (users, error) in
  guard error == nil else { return }
  guard let users = users as [CKDiscoveredUserInfo] else { return }

  for user in users {
    // use user.userRecordID to make
    // whatever connections you need
  }
}

CloudKit lives on the client. You write Swift or Objective-C code to interact with Apple's iCloud servers to save and retrieve data. But what if your app wants to support push notifications? Don’t worry CloudKit has you covered. Use CKSubscription to tell CloudKit you'd like to know when records matching a predicate are created, updated, or deleted. CloudKit will deliver a special push notification to your app denoting the change, which you can customize for your needs.

Let's take a look at setting it up: (don't forget to setup and register for push notifications!)

let publicDB = CKContainer.defaultContainer().publicCloudDatabase

let subscription = CKSubscription(
  recordType: "Spaceships",
  predicate: NSPredicate(format: "TRUEPREDICATE"),
  options: .FiresOnRecordCreation
)

let info = CKNotificationInfo()

info.alertBody = "New Spaceship Entered the Fleet!"
info.shouldBadge = true

subscription.notificationInfo = info

publicDB.saveSubscription(subscription) { record, error in }

Then when you receive the push notification use it's userInfo to create a CKNotification object and grab the ID of the new record:

let notification: CKNotification = CKNotification(
  fromRemoteNotificationDictionary: userInfo
)

if notification.notificationType == CKNotificationType.Query {
  let queryNotif = notification as! CKQueryNotification
  // do something interesting with queryNotif.recordID
}

Topics

#26: CloudKit Basics ⛅

Topics

CloudKit is Apple's API for storing and retrieving data from iCloud. With the recent introduction of the CloudKit Web Services API as well as CloudKit JS, CloudKit is now a very attracive option for the backend of your next app.

Let's take a look at the very basics of using CloudKit to create, retrieve, update and delete records:

Creating a Record

let publicDB = CKContainer.defaultContainer().publicCloudDatabase

let spaceshipRecord = CKRecord(recordType: "Spaceship")
spaceshipRecord["model"] = "T-16"
spaceshipRecord["maxSpeed"] = 1200 // in km

publicDB.saveRecord(spaceshipRecord) { (record, error) in }

Retrieving Records

let query = CKQuery(
  recordType: "Spaceships",
  predicate: NSPredicate(format: "maxSpeed > 500")
)

publicDB.performQuery(query, inZoneWithID: nil) { (records, error) in }

Updating Records

spaceshipRecord["maxSpeed"] = 1500

publicDB.saveRecord(spaceshipRecord) { (record, error) in }

Deleting Records

publicDB.deleteRecordWithID(spaceshipRecord.recordID) {
  (recordID, error) in }