Topics

#19: Protocol Extensions 🔭

Topics

Protocol Extensions are a new feature of Swift 2. They allow you to add new functions (complete with implementations) to any class that implements a protocol. The functionality is best explained with a simple example. Let’s say you wanted to add a way to shuffle arrays.

In Swift 1.2 and earlier, you’d probably add a shuffle function using something like this:

extension Array {
  mutating func shuffle() {
    for i in 0..<(count - 1) {
      let j = Int(arc4random_uniform(UInt32(count - i))) + i
      swap(&self[i], &self[j])
    }
  }
}

Which works great, but only applies to Array types. In Swift 2, you can add it to all mutable collections using Protocol Extensions.

extension MutableCollectionType where Self.Index == Int {
  mutating func shuffleInPlace() {
    let c = self.count
    for i in 0..<(c - 1) {
      let j = Int(arc4random_uniform(UInt32(c - i))) + i
      swap(&self[i], &self[j])
    }
  }
}

As you can see here, we're also able to use Swift’s fantastic pattern-matching support to limit our extension to only mutable collections that use an Int as their index.

Topics

#18: SFSafariViewController 👒🏄

Topics

SFSafariViewController is another new addition in iOS 9, and it’s a great one.

Get ready to throw out all that custom in-app browser or third-party library code you’ve been using.

Unlike other solutions, SFSafariViewController embeds all of the power of Safari on iOS (including Autofill, shared cookies, etc.) directly into your app.

Replacing your existing solutions should be trivial for most people, here’s how:

import SafariServices

class BookmarksViewController : UITableViewController {

  func didSelectBookmark(bookmark: Bookmark) {
    let vc = SFSafariViewController(
      URL: bookmark.URL,
      entersReaderIfAvailable: false
    )

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

}

SFSafariViewController Pro Tips

  • set a delegate to configure custom activity items in the share sheet
  • use the entersReaderIfAvailable property to start the user in reader view

Topics

#17: UIKeyCommand 🔑⌘

Topics

iOS has had support for hardware keyboard shortcuts for a while now. New in iOS 9, Apple has made some great improvements to how apps can take advantage of them. Your app can now register keyboard shortcuts per view controller, and all currently valid shortcuts will be neatly
summarized when the user brings up the new keyboard shortcuts view.

Wiring up a new keyboard shortcut in this system couldn't be easier. Just define a new function, create a new UIKeyCommand and then call addKeyCommand on your view controller:

// inside some UIViewController subclass:

override func viewDidLoad() {
  super.viewDidLoad()

  let shortcut = UIKeyCommand(
    input: "n",
    modifierFlags: UIKeyModifierFlags.Command,
    action: "createNewFile:"
  )

  addKeyCommand(shortcut)
}

func createNewFile(command: UIKeyCommand) {
  // do the thing
}

Topics

#16: UIStackView 🚦🚥

Topics

UIStackView changes everything. New in iOS 9, you can think of UIStackView as an abstraction layer on top of Auto Layout. It arranges it's subviews and manages their constraints for you.

One way to think about UIStackView is as the UIKit-born cousin to WKInterfaceGroup, the main layout component for building Apple Watch apps with WatchKit.

UIStackViews layout their arranged subviews horizontally or vertically, and they provide options for how arranged subviews should be aligned and distributed.

You can of course nest many UIStackViews inside each other to easily and quickly create complex layouts. Let's test it out by creating something super common like a UITableViewCell to display a comment. We'll use 2 horizontal and 1 vertical UIStackView:



UIStackView Pro Tips

  • 🌀 Set views to hidden for easy conditional layout
  • 🏃 Hide views in animation blocks to animate the re-layout
  • 🎳 Use size-classes to easily alter layout for different environments

Topics

#15: FontBlaster 📄

Topics

FontBlaster is probably one of the simplest things we'll cover in these bites.

Don't be fooled though, it is one of the most useful little utilities and has quickly made its way into my "standard set" of third-party I use in all my new projects.

Adding a custom font to an iOS app is normally just a little cumbersome:

😁 First drag the font file(s) into Xcode

😐 Open up your Info.plist

😒 Add a new key for "Fonts provided by application"

😲 Find the exact names of each font that iOS’s font system wants to hear, adding items in the .plist for each

😭 Watch as loading them all slows down your app’s initial launch

With FontBlaster, all you have to do is make sure your custom font files are added to your app's main bundle then call this method anywhere in your app:

FontBlaster.blast()

More info about FontBlaster can be found at git.io/fontblaster

Authors Note

Hey everyone, first off the response to little bites has been absolutely incredible. I'd like to thank every single one of you for reading, responding, retweeting, requesting, etc. as I get things off the ground here.

Second, I'd like to let everyone know that there will be no bites published next week as I'll be busy attending Apple's Worldwide Developer Conference learning what is sure to be tons of exciting new things that I’ll get to cover here in the coming months.

If you're going to be at the conference as well, reach out on Twitter, I'd love to meet and talk to as many of you all as I can! 👍

Bites will resume their regular schedule starting Monday June, 15th.

Cheers! 🍫
- Jake

Topics

#14: DZNEmptyDataSet 🐚

Topics

DZNEmptyDataSet is a library that makes it extremely easy to add custom "empty" data sets, for when a screen in your app either currently has no data, or the user hasn’t created any data yet.

You can configure it by implementing a bunch of different optional methods in the DZNEmptyDataSet protocol. A pretty simple setup is shown here, but the there are lots of more advanced options including custom views and even easily adding a button below the text.

extension PhotosViewController : DZNEmptyDataSetSource {
  func titleForEmptyDataSet(scrollView: UIScrollView!) -> NSAttributedString! {
    return NSAttributedString(string: "No Photos")
  }

  func descriptionForEmptyDataSet(scrollView: UIScrollView!) -> NSAttributedString! {
    let text = "Photos you take will appear here"

    return NSAttributedString(string: text, attributes: [
      NSForegroundColorAttributeName: UIColor.grayColor()
    ])
  }

  func imageForEmptyDataSet(scrollView: UIScrollView!) -> UIImage! {
    return UIImage(named: "cracked-egg")
  }
}

Detect interaction by implementing one of the methods in the DZNEmptyDataSetDelegate protocol.

extension PhotosViewController : DZNEmptyDataSetDelegate {
  func emptyDataSetDidTapButton(scrollView: UIScrollView!) {
    println("tapped button on the empty view!")
  }
}

Here's some example empty screens DZNEmptyDataSet can help you create:

Example Project

You can download a complete working project here

More Info

More info about DZNEmptyDataSet can be found at git.io/empty

Topics

#13: PromiseKit 💍

Topics

PromiseKit is a library that adds Promises support to Objective-C and Swift. Promises (sometimes referred to as "Futures") can help clean up some of the spaghetti-callback-ridden code that we've all written then never talked about again.

The main thing you should take away from this bite is that PromiseKit lets you turn repulsive, close to gibberesh code like this:

SynapseAPI.loadVideos { (videos, error) in
  if let e = error { showError(e) } else {
    var completed = 0
    var total = videos.length

    for video in videos {
      PiedPiperAPI.loadVideo { loadedVideo in
        completed++
        if completed == total {
          // finally done, UGH SRSLY
        }
      }
    }
  }
}

...into much more readable, and composable code like this:

firstly {
  SynapseAPI.loadVideos()
}.then { videos in
  // `join` waits for all the requests to finish, then continues
  join(videos.map { PiedPiperAPI.loadVideo($0) })
}.catch { error in
  // called if any error happens anywhere in the chain
}.finally {
  // done, much nicer
}

The power of Promises doesn't stop at network calls! PromiseKit comes with extensions Promise-ifying many of the traditionally disperate parts of Cocoa and Cocoa Touch like CLLocationManager, UIAlertView, and even sending a user off to another UIViewController, letting them choose something, then continuing on.

More info about PromiseKit can be found at git.io/promisekit

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

#11: Carthage 🗿

Topics

Carthage is a new way to integrate third-party libraries in your apps. One way to think of it is as a simpler alternative to CocoaPods. It’s a dependency manager distilled down to just about it’s simplest possible form.

One of the notable differences between Carthage and CocoaPods is that Carthage doesn’t require an Xcode Workspace to be created or "automagically" managed. It can be installed via a downloadable installer or with a simple brew install carthage.

Create a Cartfile, which is just a plain text file filled with lines like the ones below. Each of these tell Carthage about a library you’d like to use in your app.

github 'Alamofire/Alamofire'
github 'Hearst-DD/ObjectMapper'
github 'Haneke/HanekeSwift'

Then, simply run carthage update. At which point Carthage will go out and download and build all the libraries you asked for.

Note: Similar to CocoaPods’ pod install, you’ll also need to run carthage update anytime you change or update your Cartfile.

Where things begin to feel different from CocoaPods is that the next step is to manually drag and drop the frameworks Carthage built when you ran carthage update into the Linked Frameworks and Libraries of your Xcode target’s General settings panel.

Finally, import the libraries in your code:

import Alamofire
import ObjectMapper
import Haneke

More info about Carthage can be found at git.io/carthage

Topics

#10: Action Extensions 🎥

Topics

Action Extensions are incredibly powerful. Not only can they accept many different forms of input data, but they can return data back to the original application as well. Let's build an Action Extension to help us sound smart while writing. It will accept a word, let us choose a more intelligent sounding word, then return our selection to the original app.



First we'll add the extension:

File > New > Target... then use the Action extension template.

We grab the input word, then load replacement words:

let c = self.extensionContext!
let item = c.inputItems[0] as! NSExtensionItem
let provider = item.attachments![0] as! NSItemProvider
let textType = kUTTypeText as String

if provider.hasItemConformingToTypeIdentifier(textType) {
  provider.loadItemForTypeIdentifier(textType, options: nil) { (string, error) in
    if let word = string as? String {
      self.loadSmarterSoundingWords(word) {
        dispatch_async(dispatch_get_main_queue()) {
          self.tableView.reloadData()
        }
      }
    }
  }
}

Finally, we'll return our chosen replacement word back to the original app:

func completeWithWord(word: String) {
  var c = self.extensionContext!
  var typeID = kUTTypeText as String
  var provider = NSItemProvider(item: word, typeIdentifier: typeID)

  var item = NSExtensionItem()
  item.attachments = [provider]

  c.completeRequestReturningItems([item], completionHandler: nil)
}

// then inside didSelectRowAtIndexPath:
completeWithWord(words[indexPath.row])

You can download a complete working project here.

Run the MakeMeSmarter scheme. Select a word to replace, then press the action button.

Bonus: The project also shows how to retrieve the value back from an extension.

Page 37 of 38