Topics

#29: Handoff 👋

Topics

Handoff is part of Apple's Continuity system. It allows users to begin a task or activity on one device, then continue it on another one. It's actually extremely simple to implement, all you do is tell the system what the user is currently doing in your app, and the system handles the rest. Here it is in code:

class EditSpaceshipViewController: UIViewController {
  func createActivity() {
    let activity = NSUserActivity(
      activityType: "com.littlebites.spaceships"
    )

    activity.title = "Edit Spaceship"

    activity.addUserInfoEntriesFromDictionary([
      "spaceshipID" : spaceship.spaceshipID
    ])

    activity.becomeCurrent()
  }

  override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    createActivity()
  }
}

Now, if the user picks up another device the activity your app's icon will appear in the bottom left of the user's lock screen on iOS, or in the dock on OS X, and the user can continue where they left off. To wrap things up, we just need to implement two additional UIApplicationDelegate methods:

func application(application: UIApplication, willContinueUserActivityWithType userActivityType: String) -> Bool {

  return true
}

func application(application: UIApplication,  continueUserActivity userActivity: NSUserActivity,  restorationHandler: ([AnyObject]?) -> Void) -> Bool {

  // use the userActivity object to restore the state
  // needed for the user to continue what they were doing

  return true
}

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 }

Topics

#25: Picture in Picture 📺

Topics

One of the coolest new features in iOS 9 is the new Picture in Picture functionality on iPad. This lets users watch video content from an app even while it's in the background.

To support it in our app, we'll first make sure you set the Playback audio category in our application(application:didFinishLaunchingWithOptions:) function:

do {
  try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch { }

Then we'll use AVPlayerViewController to play video content. Picture in Picture mode will automatically kick-in if our app enters background but only if: 1.) our player is full screen, 2.) video content is playing in it, and 3.) Picture in Picture is supported on the device.

Next we'll implement this wonderfully long delegate method to restore our player UI when the user returns from Picture in Picture mode:

func playerViewController(playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: (Bool) -> Void) {
  navigationController?.presentViewController(playerViewController, animated: true) {
    completionHandler(true)
  }
}

More About PIP

  • If we need to support a generic AVPlayerLayer, AVKit also includes a new AVPictureInPictureController.

  • We also get PIP for free in WKWebView assuming our app has the Playback audio session category set.

Topics

#24: Contacts and Contacts UI 👥

Topics

Interacting with a user's Contacts database used to be, shall we say, "less than ideal". The AddressBook framework was great for it’s time, but it’s a bit past it's prime these days.

Contacts and Contacts UI are two new frameworks in iOS 9 (and OS X + watchOS) that make integrating Contact data into your app a breeze.

Here's how easy it is to search a user’s contacts and present one for viewing:

func presentContactMatchingName(name: String) throws {
    let predicate = CNContact.predicateForContactsMatchingName(name)
    let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey]
    let store = CNContactStore()

    let contacts = try store.unifiedContactsMatchingPredicate(
        predicate, 
        keysToFetch: keysToFetch
    )

    if let firstContact = contacts.first {
        let viewController = CNContactViewController(forContact: firstContact)
        viewController.contactStore = self.store

        presentViewController(viewController, animated: true, completion: nil)
    }
}

Also, Apple has officially deprecated AddressBook and AddressBookUI so now's the time to make the switch!

Topics

#23: Core Spotlight 🔍

Topics

Core Spotlight allows your content to appear in the results of system-level Spotlight searches.

let attrSet = CSSearchableItemAttributeSet(
  itemContentType: kUTTypeText as String
)

attrSet.title = "#23: CoreSpotlight"
attrSet.contentDescription = "CoreSpotlight allows your content to appear in the results of system-level Spotlight searches."

let item = CSSearchableItem(
  uniqueIdentifier: "023",
  domainIdentifier: "com.lilbitesofcocoa.bites",
  attributeSet: attrSet
)

CSSearchableIndex
  .defaultSearchableIndex()
  .indexSearchableItems([item]) { error in
    print("Success!")
  }

Of course Core Spotlight is just one of many ways to get your content into search results, be sure to also look into the NSUserActivity APIs as well as Apple's Web markup guides.

Topics

#22: Pattern Matching 🔠🔢

Topics

Patterns are one of the most powerful parts of Swift. Let's look at a few examples of cool things you can do with pattern matching.

Ranges

let since: NSTimeInterval = 127 // 2 minutes, 7 seconds

switch since {
    case (0...10): print("just now")
    case (10...60): print("a minute ago")
    default: print("\(since) seconds")
}

Enums

enum Result {
  case Success
  case Error(String)
}

let r = Result.Success

switch r {
  case .Success: print("Yay!")
  case .Error(let err): print("Boo: \(err)")
}

Types

let something = Product()

switch something {
    case is Product: print("Found a product")
    case let person as Person: print("Found a person: \(person.name)")
    default: print("Found an unknown thing.")
}

Where

let contentOffset = (0, 30.0)

switch contentOffset {
  case let (_, y) where y < 0: print("Scrolled up")
  case let (_, y) where (0...60) ~= y: print("scrolling top area")
  default: println("just scrolling")
}

Swift 2

New in Swift 2, you can now use any Swift pattern as the clause of an if statement:

let comments = 7

if case 1...10 = commentCount {
    print("some comments")
} else if case 11..100 = commentCount {
    print("lots of comments")
}

And in Swift 2's new error handling:

do {
    try send(message: "Hello")
} catch SendError.Offline {
    print("user is offline")
} catch SendError.RateLimited {
    print("stop spamming")
} catch SendError.Blocked(let reason) {
    print("user was blocked because \(reason)")
}

CLKTextProvider is part of ClockKit in watchOS 2. It's an abstract base class of a family of classes that allow you to provide text content for a complication on Apple Watch. Visual space on the Apple Watch's screen is so constrained, that Apple has created a whole suite of classes for gracefully degrading the way values are displayed in complications, depending on how much space is available.

Essentially, you tell the class the value you want displayed, and the system handles formatting and best fitting the content for the constraints of the complication it's included in.

import ClockKit

let now = NSDate()
let units : NSCalendarUnit = [ .Weekday, .Month, .Day ]

let textProvider = CLKDateTextProvider(now, units)

// textProvider will now render one
// of these strings, depending on
// the environment its in:

// "Monday, June 22"
// "Mon, Jun 22"
// "Jun 22"
// "22"

In addition, ClockKit also contains text providers for many common values you might want to include in a complication such as:

  • Dates/times like "Monday June 22" or "10:09"
  • Relative dates/times including 3 different styles: Natural ("2HRS 11MINS") , Timer ("09:42"), and Offset ("+31 MINUTES")
  • Time intervals like "1:00-2:30PM"

There's also a generic ‘simple' text provider for gracefully degrading your own custom text content.

CLKTextProvider (as well as the rest of ClockKit) is sadly only available in watchOS at the moment. It's such a great little set of utilities, here's hoping it makes it way into iOS and OS X at some point!

Topics

#20: ReplayKit 🎥

Topics

ReplayKit in iOS 9 allows you to record a movie of what's happening on screen in your app or game. Here's a quick example of how to use it:

import ReplayKit

class GameViewController: UIViewController {
  func startRecording() {
    let recorder = RPScreenRecorder.sharedRecorder()
    recorder.startRecordingWithMicrophoneEnabled(true)
  }

  func stopRecording() {
    let recorder = RPScreenRecorder.sharedRecorder()

    recorder.stopRecordingWithHandler { (previewVC, error) in
      if let vc = previewVC {
        self.presentViewController(
          vc, 
          animated: true, 
          completion: nil
        )
      }
    }
  }
}

  • Records app audio, optionally also records microphone audio. The user is given the chance to preview and, edit , and trim the video before exporting.
  • You can't access the movie file itself. After recording, user is shown an activity view controller, which you can add custom actions to.
  • Recording is polite to battery and performance.
  • Only works A7 and A8 devices.
  • Permission from the user is required to begin recording.
  • Recording automatically excludes system UI like notifications or keyboard entry.
Page 36 of 38