Topics

#59: BRYXBanner ๐ŸŽ

Topics

BRYXBanner is a great new library by Harlan Haskins for displaying "dropdown" notifications banners. It has a very straightforward API, while remaining extremely flexible. Let's take a look at some different ways to use it.

Momentarily show a banner. By default banners are dismissed on tap, swipe, or after the duration has elapsed.

let banner = Banner(
  title: "New Mission Alert",
  subtitle: "New mission added to the schedule.",
  image: nil,
  backgroundColor: UIColor.lightGrayColor()
)

banner.show(duration: 3.0)

Without a duration, the show function caused the banner to be shown until the user taps it. Here we're passing in a closure that will be executed when the user taps the banner.

let banner = Banner(
  title: "Life Form Detected",
  subtitle: "An unknown lifeform has been found.",
  image: UIImage(named: "alien"),
  backgroundColor: UIColor.redColor()
) {
  print("banner tapped!")
}

banner.hasShadows = false
banner.show()

Disable image tinting to display all kinds of images.

banner.shouldTintImage = false

Use the provided .textColor property to change all the colors at once, or use the exposed title and detail labels to completely customize the look of each banner, including font face, size, color, etc.

More info about BRYXBanner can be found at git.io/banner

Photo Editing Extensions are a powerful way for apps to integrate with the system Photos app. Users can begin editing a photo, jump into a third-party extension, and return seamlessly back to the system editing interface. Let's try making one.

We begin by adding a new Photo Editing Extension target to our project.

Our extension will use CoreImage (covered in Bite #32) to convert a photo to black and white. The basic workflow goes like this:

  1. User launches our extension while editing inside Photos.app
  2. a PHContentEditingInput object is handed to us via the startContentEditingWithInput... function
  3. User performs the edits they would like using our view controller
  4. User taps Done button (provided by the system)
  5. System asks us for a PHContentEditingOutput object containing our changes to the image via finishContentEditingWithCompletionHandler.


Let's move on to the code.

Along with the input object, we also receive a placeholderImage which we'll use as the initial image in our own image view.

func startContentEditingWithInput(contentEditingInput: PHContentEditingInput?, placeholderImage: UIImage) {
  input = contentEditingInput
  imageView.image = placeholderImage
}

After processing, we create an output using the intial input object we were handed, and write our modified image to it's content URL.

let output = PHContentEditingOutput(contentEditingInput: input)

processedImageData.writeToURL(
  output.renderedContentURL, 
  atomically: true
)

Lastly, we describe our adjustments, so we can be a good citizen in Photos.app's non-destructive editing world.

output.adjustmentData = PHAdjustmentData(
  formatIdentifier: NSBundle.mainBundle().bundleIdentifier!,
  formatVersion: "1.0",
  data: "Grayscale".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
)

Download the complete project here: j.mp/bite058

Game Center is a great way to easily add things like leaderboards, multiplayer, challenges, etc. to your iOS or OS X game. Let's try it out by authenticating a user, then reporting a score to a leaderboard.

We start by grabbing the local player, and setting an authentication handler closure. This will be called automatically when it's first set. This will handle showing the login screen if necessary.

import GameKit

let leaderboardID = "com.magnus.beggarscanyon.womprats"

class GameViewController: UIViewController {
  func authenticateWithGameCenter() {
    // called inside viewDidLoad

    NSNotificationCenter.defaultCenter().addObserver(
      self, selector: Selector("authenticationDidChange:"),
      name: GKPlayerAuthenticationDidChangeNotificationName,
      object: nil
    )

      GKLocalPlayer.localPlayer().authenticateHandler = {
      viewController, error in

        guard let vc = viewController else { return }

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

    func authenticationDidChange(notification: NSNotification) {
    reportScore(1138) // report example score after user logs in
  }
}

After the user is logged in we call our reportScore function, which looks like this:

func reportScore(score: Int64) {
    let gkScore = GKScore(leaderboardIdentifier: leaderboardID)
    gkScore.value = score

    GKScore.reportScores([gkScore]) { error in
      guard error == nil  else { return }

    let vc = GKGameCenterViewController()
      vc.leaderboardIdentifier = leaderboardID
    vc.gameCenterDelegate = self
      vc.viewState = GKGameCenterViewControllerState.Leaderboards

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

We want to see this new score we've posted, so we finish off by creating a leaderboard view controller, and presenting it. Success!

Topics

#56: ResponseDetective ๐Ÿ”

Topics

ResponseDetective is a new debugging library from Adrian Kashivskyy and the team at Netguru for "intercepting" HTTP activity and logging it. You can configure different sets of Interceptor classes, depending on what kinds of data you'd like to see. Let's set it up:

The first thing we'll need to do is register some request and response Interceptors. ResponseDetective comes with quite a few Interceptors out of the box, and you can of course create your own. Here we'll log all the headers of all requests and responses, as well as the content of any JSON requests.

We start with the default session config, and insert the InterceptingProtocol at the front of it's protocol classes.

Finally, we create a session with our config and kick off a new task to an example API endpoint.

InterceptingProtocol.registerRequestInterceptor(HeadersInterceptor())
InterceptingProtocol.registerResponseInterceptor(HeadersInterceptor())
InterceptingProtocol.registerErrorInterceptor(HeadersInterceptor())

InterceptingProtocol.registerResponseInterceptor(JSONInterceptor())

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()

configuration.protocolClasses = map(configuration.protocolClasses, {
  (var protocolClasses) in
    protocolClasses.insert(InterceptingProtocol.self, atIndex: 0)
    return protocolClasses
}) ?? [InterceptingProtocol.self]

let session = NSURLSession(configuration: configuration)

session.dataTaskWithRequest(
    NSURLRequest(URL: NSURL(string: "http://httpbin.org/get")!)
).resume()

Now, if we look at our console, we can see that all the HTTP activity has been logged:

GET http://httpbin.org/get

200 no error
Content-Length: 304
Server: nginx
Content-Type: application/json
Access-Control-Allow-Origin: *
Date: Mon, 10 Aug 2015 04:11:45 GMT
Access-Control-Allow-Credentials: true
Connection: keep-alive
{
  "args" : {

  },
  "headers" : {
    "User-Agent" : "056-responsedetective\/1 CFNetwork\/711.4.6 Darwin\/14.4.0",
    "Accept-Encoding" : "gzip, deflate",
    "Host" : "httpbin.org",
    "Accept-Language" : "en-us",
    "Accept" : "*\/*"
  },
  "origin" : "76.102.27.127",
  "url" : "http:\/\/httpbin.org\/get"
}

More info about ResponseDetective can be found at git.io/responsedetective

Using a UISegmentedControl to switch view controllers is very common, even Apple does it:

Let's build this using UIPageViewController and UISegmentedControl.

We start with a blank storyboard, drag out a UIPageViewController and set it as the initial view controller. Then drag out two UIViewControllers to switch between. We'll select the page view controller and choose Editor > Embed In > Navigation Controller.

Then we drag out a UISegmentedControl and drop it into the title area of the navigation bar of our page view controller. Finally, we create an @IBAction function that will get called when the selected segment changes.

The code is pretty straightforward, note the use of R.swift to grab those two view controllers from our storyboard. (covered in Bite #52).

func setViewControllerForIndex(index: Int) {
  setViewControllers(
    [index == 0 ? spaceshipsVC! : crewVC!], 
    direction: .Forward, 
    animated: false, 
    completion: nil
  )
}

override func viewDidLoad() {
  super.viewDidLoad()

  spaceshipsVC = R.storyboard.main.spaceshipsVC
  crewVC = R.storyboard.main.crewVC

  setViewControllerForIndex(0)
}

@IBAction func segmentedControlChanged(sender: UISegmentedControl) {
  setVCForIndex(sender.selectedSegmentIndex)
}

extension ViewController : UIPageViewControllerDataSource {
  func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
    if viewController == crewVC { return spaceshipsVC }

    return nil
  }

  func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
    if viewController == spaceshipsVC { return crewVC }

    return nil
  }
}

Download the complete project here: j.mp/bite055

Topics

#54: Dynamic Type Basics ๐Ÿ“„

Topics

Dynamic Type is a system introduced back in iOS 7 with the intention of unifying and simplifying how users change their preference for how big text should be on the screen. While the user choice is simple, under the hood Dynamic Type earns its name by dynamically adjusting things such as spacing between characters and character weights to make text as readable as possible.

Letโ€™s take a look at how to add simple Dynamic Type support.

Text Styles

The main way you interact with Dynamic Type is through the built-in set of text styles. As of iOS 9, 9 styles are available to choose from:

  • UIFontTextStyleTitle1
  • UIFontTextStyleTitle2
  • UIFontTextStyleTitle3
  • UIFontTextStyleHeadline
  • UIFontTextStyleSubheadline
  • UIFontTextStyleBody
  • UIFontTextStyleFootnote
  • UIFontTextStyleCaption1
  • UIFontTextStyleCaption2

Retrieving Fonts

If youโ€™re using the system font, all you need is:

UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)

For custom fonts, youโ€™ll need to grab a font descriptor first, then create the UIFont manually:

let fontSize = UIFontDescriptor
  .preferredFontDescriptorWithTextStyle(UIFontTextStyleHeadline)
  .pointSize

let font = UIFont(name: "Avenir Next", size: fontSize)

Lastly, you'll want to update your UI when the user changes their preferences in Settings.app. Observe this notification and trigger a re-layout when it occurs:

NSNotificationCenter.defaultCenter().addObserver(self,
  selector: "preferredContentSizeChanged:",
  name: UIContentSizeCategoryDidChangeNotification, object: nil)

Topics

#53: Signatures with Jot ๐Ÿ–‹

Topics

Jot is an awesome library from Laura Skelton and the team at IFTTT.

It makes it easy to add drawing and text overlay capabilities to your app. Today we're going to use it to build a view controller that will allow our users to sign their name on the screen using their finger. Let's get started.

We start with the "Single View" template and import jot.

We instantiate a new JotViewController, and add it as a child view controller of our SignatureViewController. We'll do this inside viewDidLoad:

class SignatureViewController: UIViewController {
  var jotViewController: JotViewController?

  override func viewDidLoad() {
    super.viewDidLoad()

    jotViewController = JotViewController()
    addChildViewController(jotViewController!)

    view.addSubview(jotViewController!.view)
    jotViewController!.didMoveToParentViewController(self)
    jotViewController!.view.frame = view.frame
  }
}

Now comes the fun part. We set the JotViewController's state to be .Drawing and its drawing color to black:

jotViewController!.drawingColor = UIColor.blackColor()
jotViewController!.state = .Drawing

If you launch the app now, you'll see you can now sign your name on the screen!

We finish things off with a Done button and some instructions. Finally, we can grab the image of the signature like this:

jotViewController!.renderImageOnColor(UIColor.whiteColor())

Download the final project here: j.mp/bite053

More info about Jot can be found at git.io/jot

Topics

#52: R.swift ๐ŸŽจ

Topics

R.swift is a tool by Mathijs Kadijk that takes the resources in your app (images, segues, storyboards, nibs, etc.), examines them and generates a file in the root of your project called R.generated.swift containing a simple struct that maps your resources' actual names to Swift properties. R.swift updates the file each time you build, so you can focus on using your resources instead of managing them.

Why do this? For starters, it gives us Xcode native autocompleteion of resources names (finally). We're also saved from hardcoding strings throughout our app, or needing to go update a set of constants somewhere everytime we add a resource.

Let's look at some examples of where R.swift comes in handy. First up, images:

if spaceship.model == SpaceshipModel.Xwing {
  cell?.imageView!.image = R.image.shipXwing
} else {
  cell?.imageView!.image = R.image.shipGeneric
}

Nibs are also much easier to work with, here we instantiate the top view in a nib:

let view = R.nib.controlPanelView.instantiateWithOwner(nil, options: nil)

Instantiate view controllers from Storyboards with ease:

let spaceshipVC = R.storyboard.main.spaceshipViewController

R.swift also makes it easy to work with our cell reuse identifiers. It even includes extensions for UITableView and UICollectionView that save you from casting your cell's type:

tableView.dequeueReusableCellWithIdentifier(
  R.reuseIdentifier.shipTableViewCell, 
  forIndexPath: indexPath
)

More info about R.swift can be found at git.io/rswift

PINRemoteImage is a new and promising solution for asynchronously downloading images. It was created by the team at Pinterest and has been battle tested in their extremely popular iOS app. Let's import PINRemoteImage, take a look at how to use it, and see what it can do:

let imageURL = NSURL(string: "http://apple.com/iCar.png")
imageView.setImageFromURL(imageURL)

Images are downloaded and decoded off the main thread. They're then cached using a fast, non-deadlocking parallel object cache. (Courtesy of another Pinterest library, PINCache). Upon subsequent requests the in-memory, then disk caches will be checked before re-downloading.

Processing

You can process the image before caching occurs. Here we'll use Toucan (covered in Bite #40) to convert the image into a circular avatar.

imageView.setImageFromURL(
  imageURL, 
  placeholderImage: placeholder, 
  processorKey: "circleAvatar"
) { (result, cost) -> UIImage! in
  return Toucan.Mask.maskImageWithEllipse(result.image)
}

Progressive JPEGs

Optionally, you can enable a mode supporting progressive JPEG images. They're treated with a nice blur effect, so they look great even at low resolutions.

imageView.updateWithProgress = true

Animated GIF Support

PINRemoteImage also comes with built-in support for Flipboard's blazing fast animated GIF library, FLAnimatedImage. All you have to do to use it is create an FLAnimatedImageView instead of a UIImageView:

let animatedImageView = FLAnimatedImageView(frame: GIFRect)

let GIFURL = NSURL(string: "http://apple.com/eddy-dance.gif")
animatedImageView.setImageFromURL(GIFURL)

// #GIFnotJIF

More info about PINRemoteImage can be found at git.io/pri

Let's say you have a table view with some cells showing a list of crew members. It'd be great if when the user taps a cell, it would โ€œexpandโ€ to reveal some actions that can be performed on that crew member. Let's dive in.

Setup View Hierarchy

As you can see, we lean on UIStackView pretty heavily. The constraint shown here is the one we'll be animating.

Animate the Constraint

In our cell's setSelected(animated:) function, we animate the constraint just like we covered back in Bite #9. After the animation's done, we hide/show the toolbar. This triggers the top-level stack view to recalculate it's height.

let constant: CGFloat = selected ? 30.0 : 0.0
let options = [.AllowUserInteraction, .BeginFromCurrentState]

UIView.animateWithDuration(0.3, delay: 0.0, options: options, animations: {
  self.toolbarStackViewHeightConstraint.constant = constant
  self.layoutIfNeeded()
}, completion: { completed in
  self.toolbarStackView.hidden = !selected
})

Final Product

Our view controller takes care of resizing our cells by updating the 'expanded' state of each crew member when the selection changes and a pair of calls to tableView.beginUpdates/endUpdates inside the tableView:didSelectRowAtIndexPath: and tableView:didDeselectRowAtIndexPath:.

Download the complete working project here: j.mp/bite050

Page 33 of 38