Active Filters: Foundation

Topics

#78: NSURLSession Basics 🔗

Topics

NSURLSession is the heart of networking in Foundation. Let's take a look at a few ways to use it.

The basic idea is we'll create a new NSURLSession, and then use it to create NSURLSessionTasks that will make the actual HTTP requests. We'll start by creating a new session:

let session = NSURLSession(
  configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
  delegate: self,
  delegateQueue: nil
)

Now we'll use the session to create different tasks to do our bidding:

Call HTTP API

let url = NSURL(string: "http://api.apple.com/releaseSchedule")!
let task = session.dataTaskWithURL(url)
task.resume()

Download Data

We'll can use a data task like above, then use this function to grab the downloaded data. This will be called multiple times, so we'll need to keep an NSMutableData reference around to append to.

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData)

Upload Data

Here we're uploading an NSData. There's also another form of uploadTaskWithRequest that accepts a file URL to upload from.

let request = NSMutableURLRequest(
  URL: NSURL(string: "http://api.someservice.com/upload")!
)

request.HTTPMethod = "POST"

let task = session.uploadTaskWithRequest(request, 
  fromData: dataToUpload)

task.resume()

Imagine an app that downloads and plays movies. If a user begins downloading a movie, it'd be great if the file could continue downloading even if the user presses the home button and sends the app to the background. NSURLSessionDownloadTask can help. Let's take a look.

First we create a new background configuration and session. Then we use that session to create a new download task:

class ViewController: UIViewController, NSURLSessionDownloadDelegate, NSURLSessionTaskDelegate {
    func downloadMovieFile() {
    let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("downloads-session")
    let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)

    let task = session.downloadTaskWithURL(NSURL(string: "http://supersecret.disney.com/movies/episodeVII.mp4")!)
    task.resume()
  }
}

We can grab the downloaded data pretty easily, by implementing this function from NSURLSessionDownloadDelegate:

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
    print("Movie data written to \(location)")
}

Last but not least, we'll need to implement a function from NSURLSessionTaskDelegate. In it we'll call finishTasksAndInvalidate on the session. This ensures everything is cleaned up and the session lets go of us (self) as its delegate.

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
  if let error = error { print("Download Error: \(error)") }
  session.finishTasksAndInvalidate()
}

Topics

#66: NSTimeZone ⏰

Topics

Ask any engineer, time zones are the worst.

Don't worry, NSTimeZone has our backs. It can help us easily translate NSDate objects from one time zone to another. Let's dive deeper with a concrete example.

Here's a common scenario:

We're working on an app that downloads some data from an API, then displays it to the user. In our case, we know the API is returning timestamp information in the GMT time zone.

Translating the API's values to the user's local time zone would go something like this:

let input = "2015-08-24T09:42:00" // came from HTTP API

let GMTTZ = NSTimeZone(forSecondsFromGMT: 0)
let localTZ = NSTimeZone.localTimeZone()

let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
formatter.timeZone = GMTTZ

if let date = formatter.dateFromString(input) {
    formatter.timeZone = localTZ
    formatter.dateFormat = "MMMM d, H:mm a"

    timestampLabel.text = formatter.stringFromDate(date)
  // "August 24, 2:42 AM"
}

After we parse the date, we change our formatter's time zone to the local one, then render our date back to a string to show the user.

The NSTimeZone.localTimeZone() function (as well as any NSTimeZone object) is our gateway to a few more useful values:

localTZ.secondsFromGMT // -25200
localTZ.abbreviation // "PDT"
localTZ.name // "America/Los_Angeles"

There's also a way to get a more human-friendly name, with a few built-in styles. We're using .Generic here:

localTZ.localizedName(
  .Generic,
  locale: NSLocale.currentLocale()
) // "Pacific Time"

Don't let the insanity of time zones scare you away from making your app work well in any location. Remember, Foundation has your back.

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

#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

#12: NSLinguisticTagger 🎤

Topics

NSLinguisticTagger is one of the hidden gems of Foundation.

It can be used to process natural-language text (i.e. words written by a human and suitable for consumption by other humans).

It’s an incredibly powerful API and we’re only going to scratch the surface of what’s possible with it here.

Let’s use NSLinguisticTagger to extract all the proper nouns from a UITextView.

Then we’ll use those nouns to perform searches in the Wolfram Alpha API to provide instant feedback about the important things the user has written in the text view.

The result will be a bare-bones context-aware writing app that can understand what you're writing about!

First we extract the proper nouns, like this:

func updateProperNouns() -> [String] {
  let options: NSLinguisticTaggerOptions = .OmitWhitespace | .OmitPunctuation | .JoinNames
  let schemes = NSLinguisticTagger.availableTagSchemesForLanguage("en")
  let tagger = NSLinguisticTagger(tagSchemes: schemes, options: Int(options.rawValue))

  tagger.string = inputTextView.text

  var results = ""
    let properNouns = []

  let range = NSMakeRange(0, (inputTextView.text as NSString).length)

  tagger.enumerateTagsInRange(range, scheme: NSLinguisticTagSchemeNameType, options: options) {
    (tag, tokenRange, sentenceRange, _) in
          let token = (self.inputTextView.text as NSString).substringWithRange(tokenRange)
          results += "\(token): \(tag)\n"

      let properNounTags = [
        NSLinguisticTagPersonalName,
        NSLinguisticTagPlaceName,
        NSLinguisticTagOrganizationName
      ]

      if contains(properNounTags, tag) {
      properNouns.append(token)   }

    return properNouns
  }
}

Then, we grab the first noun and search for it, and place the resulting description into our output text view:

if let query = properNouns.first {
  API.search(query) { (thingDescription, error) in
    self.outputTextView.text = thingDescription
  }
}

Here's what it looks like:

Download a working demo project here

Read More About NSLinguisticTagger

Topics

#3: Timepiece 🕙

Topics

Timepiece is a library from Naoto Kaneko that makes working with NSDate objects a bit more intuitive.

let now = NSDate()
let theForceAwakens = now + 218.days + 2.hours + 34.minutes

It also aids in quickly parsing dates from or rendering dates to a String:

let judgementDay = "1997-08-29".dateFromFormat("yyyy-MM-dd")
1.day.later.stringFromFormat("EEEE") // "Thursday"

Comparisons are simplified as well:

if (birthday > 40.years.ago) {
  midLifeCrisis()
}

How to Use Timepiece

In our Podfile:

platform :ios, '8.0'

pod 'Timepiece'

In any Swift file:

import Timepiece

More info about Timepiece can be found at git.io/timepiece

Page 3 of 3