Topics

#79: Static Shortcut Items 📌

Topics

Shortcut Items were introduced with the announcement of 3D Touch on iPhone 6S and iPhone 6S Plus. They allow users to quickly jump to a particular section or feature of an app, straight from the home screen.

Shortcut Items come in two forms: Static, and Dynamic. Static shortcut items live in the Info.plist of our project. Dynamic shortcut items are defined in code, and configured at run time. Let's take a look at how to implement static shortcut items.

The first step is to configure the shortcut items in our Info.plist file:

Then, we need to implement one new function on our app delegate. We're using JLRoutes here (covered in Bite #62) to run some code when a given URL is passed in.

Then we'll call the provided completionHandler, with a Bool indicating whether we were able to handle the item.

func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: Bool -> Void) {
  let url = NSURL(string: shortcutItem.userInfo["url"])!

  let didHandle = JLRoutes.routeURL(url)

  completionHandler(didHandle)
}

Note: At publish time of this Bite, Xcode did not offer a way to test shortcut items in the Simulator. To test shortcut items' functionality, this project by Conrad Kramer was used.

UPDATE: We covered Dynamic Shortcut Items in Bite #88. Head there to read about how to modify an app's shortcut items in code, at runtime.

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

#76: Background Fetch 🔄

Topics

It's very common for an app to need to regularly check for new data. Background Fetch allows us to do just that, while running in the background.

The system will periodically wake our app up and ask it to fetch new data. Once awoken, we'll be able to run whatever code we need to check for or fetch new data.

Lastly, we'll need to tell the system what the result of our fetch was. The system will use our response to decide how frequently our app needs to fetch.

Let's start by going to the capabilities tab of our project, and turning on Background Modes. Then we'll check the box next to the Background fetch mode.

Next, the code. We'll set a minimum background fetch interval in our didFinishLaunching function.

The performFetchWithCompletionHandler function is the last piece of the puzzle. We fetch any new data that might exist, and call the provided completionHandler with an enum describing the result.

func application(application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
  application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
  return true
}

func application(application: UIApplication, 
  performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)) {

  JEDIGossipAPI.fetchStories() { (stories: [JEDIGossipStory], error: NSError?) in
    guard stories.count > 0 else { completionHandler(.NoData); return }
    guard error == nil else { completionHandler(.Failed); return }

    completionHandler(.NewData)
  }
}

Calling the completion handler will also trigger the system to take a new snapshot of our app for the multitasking interface, so we should make sure we've updated any views that need it beforehand.

To test everything, we can use the Debug > Simulate Background Fetch command in the Simulator, or create a background fetch-specific scheme like we covered in Bite #65.

Topics

#75: Locksmith 🔐

Topics

Locksmith is a library from Matthew Palmer that really improves how we can interact with the Keychain. Let's look at the basics:

Reading Data

let dictionary = Locksmith.loadDataForUserAccount("kyloren")

Writing Data

do {
  try Locksmith.saveData(
    ["saberColor": "#FF0000"],
    forUserAccount: "kyloren"
  )
} catch { }

Updating Data

do {
  try Locksmith.updateData(
    ["saberColor": "#30FF00"],
    forUserAccount: "kyloren"
  )
} catch { }

Deleting Data

do {
  try Locksmith.deleteDataForUserAccount("kyloren")
} catch { }

That's just the beginning though. Locksmith supports almost every feature Keychain has to offer. It also embraces protocols, and protocol extensions. Let's look at an example of how to improve our own types, making them easier to store in the Keychain.

Let's say we have a simple struct in our app to represent user accounts. We'll implement a couple of protocols from Locksmith on it:

struct JediTempleAccount: GenericPasswordSecureStorable,
  CreateableSecureStorable, ReadableSecureStorable,
  DeleteableSecureStorable {

  let username: String, password: String

  let service = "jedi-temple"
  var account: String { return username }
  var data: [String: AnyObject] { return ["password": password] }
}

Now we can work with accounts and the Keychain like this:

let account = JediTempleAccount(username: "kyloren", password: "yaysith")

do { try account.createInSecureStore() } catch { } // save
let result = account.readFromSecureStore() // retrieve
do { try account.deleteFromSecureStore() } catch { } // delete

More info about Locksmith can be found at git.io/locksmith

Topics

#74: Static 📺

Topics

Static is a library from Venmo that makes it very simple to create simple static-content table views. Let's take a look at how to use it:

The basic idea of Static is that we'll configure what is displayed in our table view by supplying Section and Row objects to a DataSource object:

class SettingsViewController: TableViewController {
    override func viewDidLoad() {
    super.viewDidLoad()

    dataSource.sections = [
      Section(rows: [
        Row(text: "Jake Marsh", detailText: "@jakemarsh",
          cellClass: SubtitleCell.self,
          selection: { [unowned self] in
            // edit profile
          }
        ),
        Row(
          text: "Cellular Downloads",
          accessory: .View(cellularDownloadsSwitch)
        )],
        footer: .Title("Version: \(appVersion)")
      )
    ]
  }
}

Custom accessories are simple to implement with Static:

Row(text: "Enable Hyperdrive", accessory: .DetailButton({
  // configure hyperdrive settings
})),

Note how we not only give the cell a detail button, but we pass in a closure to be run when the detail button is tapped. We can even use a custom UIView as an accessory like we did with the switch in our first example.

We used a simple string as our footer here, but we can easily use a view instead:

Section(rows: [], footer: .View(footerView)).

Static does a great job separating concerns. Rows don't know anything about the actual UITableViewCells that eventually get shown to represent them. Static uses the .Value1 cell type by default and comes with a few other standard cell types. We can easily add our own by implementing the CellType protocol on any of our own UITableViewCell subclasses.

More info about Static can be found at git.io/statictv

Topics

#73: UIImage Tips 🌄

Topics

UIImage is a deceptively powerful part of UIKit. Let's take a look at some of the different ways we can use it:

Template Images

Let UIKit do the heavy lifting by using template images. UIKit only looks at the alpha channel, and draws the image using the tint color of the view it's contained in.

UIImage(named: "filters-icon")!
  .imageWithRenderingMode(.AlwaysTemplate)

   

Animation

UIImages can be animated. A UIImage can contain many images within it, as well as a duration. Put the containing image in a UIImageView and call startAnimating to see it.

Pattern Images

This one's fun. We can create a UIColor from a UIImage. The resulting "color" will be our image, tiled. We can then use this UIColor as a background color of a view.

UIColor(patternImage: UIImage("bg-pattern")!)

Stretchable Images

Sometimes we don't want to write a bunch of Core Graphics code to implement things like rounded corners, inner shadows, etc. UIImage has a great feature for implementing resizable (also sometimes called 9-patch) images.

We want to implement a fancy button. All we need is a tiny little 9x9 image. In code, we'll tell UIKit to load the image, and create a resizable image from it.

let buttonBGImage = UIImage(named: "button-stretchable")!
  .resizableImageWithCapInsets(UIEdgeInsets(
    top: 4.0,
    left: 4.0,
    bottom: 4.0,
    right: 4.0
  ))

Write JPEG Data to a File

The second parameter controls quality/compression of the JPEG. Smaller numbers result in smaller but uglier image files.

let data = UIImageJPEGRepresentation(someImage, 1.0)

data?.writeToFile(imageFilePath, atomically: true)

Topics

#72: Customizing Navigation Bars 🍀

Topics

Navigation Bars are a versatile part of iOS. Let's take a look at some different ways we can customize their appearance and behavior.

Custom Fonts and Colors

let nba = UINavigationBar.appearance()
let bba = UIBarButtonItem.appearance()

let titleTextAttributes = [ NSFontAttributeName : navBarFont ]

nba.barTintColor = green
nba.tintColor = darkGreen
nba.titleTextAttributes = titleTextAttributes

bba.setTitleTextAttributes(titleTextAttributes, forState: .Normal)

Hide/Show Navigation Bar when Scrolling

navigationController?.hidesBarsOnSwipe = true

This will cause our navigation bar to slide on and off screen as the user scrolls, just like in Safari for iOS.

Empty Back Button

We want a back button with no text. We could set the title of our first view controller to an empty string, but then our first view controller would be title-less. We can get the best of both worlds by giving the first view controller a custom back button item with an empty title. We'll need to do this before we push on a new view controller.

navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)

Use an Image as a Title

let imageView = UIImageView(image: UIImage(named: "app-logo")!)
navigationItem.titleView = imageView

Topics

#71: UIActivityViewController Basics 🌼

Topics

UIActivityViewController is one of the main ways we can allow our apps to talk with each other, as well as allow our users to share content with the world. Let's take a look. UIActivityViewController accepts an array of activityItems which can be of many types:

let shareText = "#71: UIActivityViewController Basics"
let shareURL = NSURL(string: "https://littlebitesofcocoa.com/71")!
let shareImage = UIImage(named: "71-thumbnail")!

let itemsToShare = [shareText, shareURL, shareImage]

let activityVC = UIActivityViewController(
  activityItems: itemsToShare, applicationActivities: nil
)

presentViewController(activityVC, animated: true, completion: nil)

The activity view controller will intelligently use each item. For example: On Twitter, the text will become the text of a tweet. The URL and image will be appended and shared natively as an image attached to the tweet.

UIActivityViewController is pretty clever. For example, it's smart enough to know if the NSURL we hand it is to a video in the user's Photos library. In such a case, it responds by letting the user share the video with services like Vimeo. We can also pass in an NSData containing the actual video.

We can even allow printing our content by wrapping it in a UISimpleTextPrintFormatter and passing that in:

let printData = UISimpleTextPrintFormatter(text: biteContent)
let itemsToShare = [biteTitle, printData]

Finally, we can use the completeWithItemsHandler closure to learn if the user actually performed a share or action in our activity view controller, as well as what specific activity they chose.

activityVC.completionWithItemsHandler = {
  (activityType, completed, items, error) in

  guard completed else { print("User cancelled."); return }

  print("Completed With Activity Type: \(activityType)")

  if activityType == UIActivityTypePostToFacebook {
    print("Shared on Facebook")
  }
}

Topics

#70: Custom Map View Pins 🔰

Topics

MKMapView is a powerful class. One great feature is its ability to display custom annotation views for the annotations we use on our maps.

Let's test it out by creating a little treasure map. We'll start by creating a map view and adding an annotation to it:

let mapView = MKMapView(frame: mapFrame)
mapView.delegate = self

let location = CLLocationCoordinate2D(latitude: 37.3317115, longitude: -122.0301835)

let annotation = TreasureAnnotation(coordinate: location)
mapView.addAnnotation(annotation)

view.addSubview(mapView)

Instead of the standard red pin graphic 📍, we'd like to show a fun little treasure chest image at the spot of some secret treasure!

To do this, take advantage of MKAnnotationView's .image property. We can throw a regular UIImage at it to get exactly the behavior we're looking for. All we need to do is implement one delegate function and return an MKAnnotationView from it. MKAnnotationViews get reused just like UITableViewCells and UICollectionViewCells.

func mapView(mapView: MKMapView!, 
  viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
  if (annotation is MKUserLocation) { return nil }

  let reuseID = "chest"
  var v = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseID)

  if v != nil {
    v.annotation = annotation
  } else {
    v = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseID)

    v.image = UIImage(named:"chest")
  }

  return v
}

MKAnnotationView has tons of helpful properties out of the box, be sure to take a look before creating a full-blown subclass.

Page 31 of 38