Topics

#49: Realm Basics 🔮

Topics

Realm is a database made specifically for running on mobile devices. It works on Mac and iOS devices. (It even supports Android!) It's a great alternative to Core Data or even raw SQLite.

There's plenty to love about Realm, but the best part is it's ultra-simple API:

Define an Object

import RealmSwift

class Spaceship: Object {
  dynamic var name = ""
  dynamic var topSpeed = 0 // in km
}

Define a Related Object

class Spaceship: Object {
  dynamic var name = ""
  dynamic var topSpeed = 0
  dynamic var owner: User?
}

class User: Object {
  dynamic var firstName = ""
  dynamic var lastName = ""
  let spaceships = List<Spaceship>()
}

Create an Instance of an Object

var ship = Spaceship()

ship.name = "Outrider"
ship.topSpeed = 1150

var dash = User()

dash.firstName = "Dash"
dash.lastName = "Rendar"

ship.owner = dash

// need one Realm per thread
let realm = Realm()

realm.write {
  realm.add(ship)
}

Find Objects

Queries in Realm couldn't be simpler. They are chainable. You can add as many calls to .filter as you'd like. You can sort results using the chainable sorted function.

realm
  .objects(Spaceship)
  .filter("topSpeed > 1000")
  .filter("name BEGINSWITH 'O'")
  .sorted("topSpeed", ascending: false)

Performance

Realm uses a "zero-copy" design that allows it process > 35 queries a second, as well as insert > 95,000 records a second in a single transaction.

More info about Realm can be found at realm.io.

Topics

#48: MKDirections 🚩

Topics

MKDirections and MKDirectionsRequest are the classes we use to retrieve directions between two locations. They also make it very easy to display the resulting directions route on a map. Let's dive in:

Setup Directions Request

Note: Off camera, we have let the user input a keyword search String, then geocoded it into an MKPlacemark object called placemark.

let request = MKDirectionsRequest()

request.source = MKMapItem.mapItemForCurrentLocation()

let destination = MKPlacemark(
  coordinate: placemark.location.coordinate,
  addressDictionary: nil
)

request.destination = MKMapItem(placemark: destination)
let d = MKDirections(request: request)

d.calculateDirectionsWithCompletionHandler { response, error in
  guard let route = response?.routes.first else { return }

  // we'll do this bit next
}

Display Directions on Map

self.directionsRoute = route

self.mapView?.addOverlay(route.polyline)
self.mapView?.setVisibleMapRect(
  route.polyline.boundingMapRect,
  animated: true
)

Finally, we implement this MKMapViewDelegate method:

func mapView(mapView: MKMapView,
rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
  let renderer = MKPolylineRenderer(
    polyline: directionsRoute.polyline
  )

  renderer.lineWidth = 2
  renderer.strokeColor = UIColor.blueColor()

  return renderer
}

Topics

#47: MKLocalSearch 📍

Topics

MKLocalSearch is a wonderful little gem hidden inside MapKit. It allows an app to perform a natural language query for local points of interest near a given latitude and longitude. Today we'll use it to build a simple app to find us a place to eat lunch.

manager = CLLocationManager()
manager!.delegate = self
manager!.desiredAccuracy = kCLLocationAccuracyThreeKilometers
manager!.requestWhenInUseAuthorization()
manager!.requestLocation()

// then later, once we have a location:

let request = MKLocalSearchRequest()

request.naturalLanguageQuery = "Restaurants"
request.region = MKCoordinateRegionMakeWithDistance(location.coordinate, 1600, 1600)

MKLocalSearch(request: request).startWithCompletionHandler { (response, error) in
  guard error == nil else { return }
  guard let response = response else { return }
  guard response.mapItems.count > 0 else { return }

  let randomIndex = Int(arc4random_uniform(UInt32(response.mapItems.count)))
  let mapItem = response.mapItems[randomIndex]

  mapItem.openInMapsWithLaunchOptions(nil)
}

First we set up our CLLocationManager, and use the new iOS 9 method for requesting a “single location” instead of continuous updates.

Then we create the MKLocalSearchRequest object and tell it to search "Restaurants" within 1600 meters (about a mile) of the user's current location.

After that it's just a matter of waiting for results, choosing one at random, and then we cheat a bit (since this is just a demo) and pop the user out to the Maps app to view the chosen result.

Download the example project here: j.mp/bite047

Topics

#46: NSFileManager 📂

Topics

NSFileManager is one of the primary ways you interact with the file system on iOS and OS X. Today we’ll look at both how to use it, as well as how to put some of the new Swift 2 features we’ve been learning about to real world use.

guard let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first else { return }

let logPath = documentsPath + "spaceship.log"
guard let logExists = fm.fileExistsAtPath(logPath) else { return }

do {
  let attributes = try fm.attributesOfItemAtPath(logPath)
  let createdAt = attributes[NSFileCreationDate]

  // > 5 minutes (in seconds) ago?
  if let created = createdAt where fabs(created.timeIntervalSinceNow) > 300 {
    try fm.removeItemAtPath(logPath)

      "[BEGIN SPACESHIP LOG]".writeToFile(
      logPath,
      atomically: false,
      encoding: NSUTF8StringEncoding,
      error: nil
    )
  }
} catch let error as NSError {
    print("Error: \(error.localizedDescription)")
}

Here we use NSFileManager to wipe out a log file if it's more than 5 minutes old:

First we use guard to make sure our documents directory is available.

Then, we create a path for our log file, and check that it exists.

Many of NSFileManager's functions are marked as throws, so we need a set of do/catch blocks.

Next we check when the log file was created.

We use another new Swift 2 feature, pattern matching, to do our age check on the optional inside the if statement.

Finally we try to remove the file, and replace it with a fresh, blank log.

We then use the catch statement to capture and print any errors that have been thrown.

Topics

#45: Pasteboards 📋

Topics

There's a ton of great functionality packed in the UIPasteboard and NSPasteboard classes. Let's take a look at some things you can do:

Get the System Pasteboard

// iOS

let pb = UIPasteboard.generalPasteboard()

// OS X

let pb = NSPasteboard.generalPasteboard()

Copy to Clipboard

On iOS, similar convenience methods exist for images, URLs, and colors.

// iOS

pb.string = "Delicious!"

// OS X

pb.clearContents()
pb.setString("Delicious!", forType: NSPasteboardTypeString)

Read Contents

On OS X, you'll use the NSPasteboardType* family of keys.

pb.string // iOS

pb.stringForType(NSPasteboardTypeString) // OS X

Get Notified Of Changes

// iOS only. On OS X, you'll need to poll :(

NSNotificationCenter.defaultCenter().addObserver(   self,   selector: "pasteboardChanged:",
  name: UIPasteboardChangedNotification,
  object: nil
)

func pasteboardChanged(n: NSNotification) {
  if let pb = n.object as? UIPasteboard {
    print(pb.items)
  }
}

Share Data Between Apps

If you're using App Groups, you can share data between your apps by creating your own custom keyboard:

let customPB = UIPasteboard(
  name: "com.littlebites.spaceships",
  create: true
)

customPB!.persistent = true

Copy Animated GIF to Clipboard

if let GIFData = NSData(contentsOfURL: GIFURL) {
  pb.setData(
    GIFData,
      forPasteboardType: "com.compuserve.gif"
  )
}

Topics

#44: UIAlertController ⚠️

Topics

Alert Views and Action Sheets are some of the oldest parts of UIKit. In iOS 8, Apple overhauled how you create and display them using UIAlertController. Let's take a look at how it can improve the adaptability of your UI as well as the readability of your code:

@IBAction func launchButtonTapped(launchButton: UIButton) {
  let controller: UIAlertController = UIAlertController(
    title: "Where To?",
    message: "Where would you like to go?",
    preferredStyle: .ActionSheet
  )

  controller.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))

  controller.addAction(UIAlertAction(title: "Home", style: .Default) { action in
    flyToPlace("Home")
  })

  controller.addAction(UIAlertAction(title: "Pluto", style: .Default) { action in
    flyToPlace("Pluto")
  })

  controller.popoverPresentationController?.sourceView = launchButton

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

First we create the controller, and set the style to .ActionSheet. We could have used .Alert instead, and our actions would be presented as an Alert View.

Then we add actions, each with a block callback that will fire when the action is selected. A nil handler will have no action.

Then we set .sourceView to the button that originated the action so our action sheet's arrow will ‘point' to the right spot on iPad.

And finally we present our controller just like any other view controller.

Topics

#43: Local Notifications 📍

Topics

Local Notifications are a way to display a system notification to a user of your app without needing to implement a push server. You simply schedule them using an NSDate and they are presented at that time. Let's take a look.

Register for Notifications

Since iOS 8, we've had two different things to register for: User Notifications and Remote notifications. Since we'll be “sending” Local Notifications, we only need to register for User Notifications.

In didFinishLaunchingWithOptions:

application.registerUserNotificationSettings(
  UIUserNotificationSettings(
    forTypes: [.Alert, .Sound, .Badge], 
    categories: nil
  )
)

This will prompt the user for permission to see notifications, except instead of coming from a backend server, they'll be generated locally on the device.

Schedule a Local Notification

After we get the didRegisterUserNotificationSettings callback, we're ready to schedule a notification. We use the UILocalNotification class to configure all the aspects of how the notification will look and behave:

func landSpaceship() {
  autopilotPerformAction(.LandShip)

  let landingTime = 30.0 // in seconds

  let n = UILocalNotification()

  n.alertTitle = "Spaceship Landed!"
  n.alertBody = "Good news everyone! Our ship has successfully landed."

  n.timeZone = NSCalendar.currentCalendar().timeZone
  n.fireDate = NSDate(timeIntervalSinceNow: landingTime)

  UIApplication.sharedApplication().scheduleLocalNotification(n)
}

Topics

#42: HealthKit Basics 🏥

Topics

Let's use HealthKit to retrieve the user's current weight. HealthKit requires itemized authorization for each bit of data we want to read or write. Here we ask to write nothing, and read weight only.

import HealthKit

guard HKHealthStore.isHealthDataAvailable() else { return }

let store = HKHealthStore()

let weightQuantity = HKQuantityType.quantityTypeForIdentifier(
  HKQuantityTypeIdentifierBodyMass
)!

store.requestAuthorizationToShareTypes(
  nil,
  readTypes: [weightQuantity]
) { succeeded, error in
  guard succeeded && error == nil else { return; }

  // success!
}

Then the fun part: We execute a sample query, grabbing the latest weight result. Then we format the weight using NSMassFormatter.

let sortDescriptor = NSSortDescriptor(
  key: HKSampleSortIdentifierEndDate,
  ascending: false
)

let query = HKSampleQuery(
  sampleType: weightQuantity, 
  predicate: nil, 
  limit: 1, 
  sortDescriptors: [sortDescriptor]
) { query, results, sample in
  guard let results = results where results.count > 0 else { return }

  let sample = results[0] as! HKQuantitySample
  let weightInPounds = sample.quantity.doubleValueForUnit(HKUnit.poundUnit())

  let suffix = NSMassFormatter().unitStringFromValue(
    weightInPounds,
    unit: .Pound
  )

  let formattedWeight = NSNumberFormatter.localizedStringFromNumber(
    NSNumber(double: weightInPounds),
    numberStyle: .NoStyle
  )

  print("User currently weighs: \(formattedWeight) \(suffix)")
}

store.executeQuery(query)

Topics

#41: CMPedometer 👣

Topics

CMPedometer is part of the Core Motion framework. One of the features it provides is the ability to retrieve the number of steps a user has taken during a given time period. Let’s take a look at how to do it:

import Timepiece

let pedometer = CMPedometer()

if CMPedometer.isStepCountingAvailable() {
    // NSDate.beginningOfDay comes from the Timepiece library

    pedometer.queryPedometerDataFromDate(NSDate.beginningOfDay, toDate: NSDate()) { data, error in
      guard let data = data else { return }

      print("The user has taken \(data.numberOfSteps) so far today, that's \(data.distance) meters!")
    }
}

We first import the Timepiece library (covered in Bite #3) and create a pedometer object. Then we check if the device supports step counting, and then we ask for the pedometer data since the beginning of today. The call hands back a CMPedometerData object which has properties representing the number of steps taken and the distance traveled.

Topics

#40: Toucan 🐧

Topics

Whether you're applying a circular crop to user avatars or just resizing a photo downloaded from a web service, processing images can be a bit of a chore. Toucan is a Swift image processing library from Gavin Bunney that makes working with images a breeze.

Let's import Toucan and take a look at what it can do:

Resize Images

// userAvatar is a UIImage downloaded from the network

let resizedAvatar = Toucan.Resize.resizeImage(
    myImage,
    size: CGSize(width: 100, height: 100)
)

Apply a Circular Mask

let circularAvatar = Toucan.maskWithEllipse(resizedAvatar)

Apply a Rounded Rectangle Mask

let roundedAvatar = Toucan.maskWithRoundedRect(height: 5)

Image Borders & Method Chaining

Toucan provides another syntax for chaining different processing steps together. Just call .image at the end to get a final processed UIImage. Here we can also see how to apply a 1 point wide border to the final image.

Toucan(image: userAvatar)
  .resize(CGSize(width: 100, height: 100))
  .maskWithEllipse(
    borderWidth: 1, 
    borderColor: UIColor.lightGrayColor()
  )
  .image

More info about Toucan can be found at git.io/toucan

Page 34 of 38