Active Filters: UIKit

Setting our app apart can be tricky. We don't want to deviate too far from established conventions, but we don't want to blend in to the crowd either. Small amounts of "polish" to an app's UI can go a long way to making it feel special.

Today we'll check out one more tool in our arsenal for doing this. It's a library from Andrea Mazzini called SubtleVolume.

The idea here is to improve upon the default, screen-obscuring, built-in volume overlay. iOS's system interface is fine for most apps, but often it can get in the way of the content the underlying app is presenting.

SubtleVolume solves this by providing an elegant volume view that we can put anywhere on the screen. Let's try it out:

let volume = SubtleVolume(style: .Plain)

volume.frame = CGRect(x: 0, y: 20, width: w, height: 4)
volume.barTintColor = .whiteColor()
volume.barBackgroundColor = UIColor(white: 1.0, alpha: 0.3)
volume.animation = .FadeIn

view.addSubview(volume)

Now, the view will automatically hide/show when the user changes the volume, Neat!

More info about SubtleVolume can be found at git.io/subtlevolume

The real world doesn't have straight edges. Things are rounded. Our UIViews can be too! Most of us know about the cornerRadius property on CALayer. We can set a value, then see all the corners round themselves to that value:

contentView.layer.cornerRadius = 8

Nice, but what if we only want to round some of the corners though?

let cornerRadius: CGFloat = 8
let maskLayer = CAShapeLayer()

maskLayer.path = UIBezierPath(
  roundedRect: view.bounds,
  byRoundingCorners: [.BottomLeft, .BottomRight],
  cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)
).CGPath

view.layer.mask = maskLayer

For this, we'll need to get a bit more creative. We can use an often-overlooked type called UIRectCorner.

It lets us describe which corners we'd like to round. Then we'll use a UIBezierPath to create a "mask" layer that will only allow some of the content to "show through".

We pass in an option set for the byRoundingCorners parameter, listing out the corners we'd like rounded.

Success! Happy rounding!

Update: Shout-out to Reddit-reader /u/cuomo456 who reminds us to update/replace our layer masks anytime the view/layer we're masking's frame or bounds changes. If we don't, we could find ourselves scratching our heads why our rounded corners aren't working in a UITableViewCell, for example.

Today we'll look at an interesting library from Evan Dekhayser called KBKit. It extends UIKit to add support for navigating around using a physical keyboard. (Think either bluetooth or other keyboard connected to an iPad, but would also work on iPhone.) Let's take a look.

KBKit consists of a few subclasses of common UIKit classes. KBTableView adds Up Arrow, Down Arrow, and Return Key functionality to UITableView. Very cool.

let tableView = KBTableView(frame: CGRect.zero, style: .Plain)

tableView.onSelection = { (indexPath: NSIndexPath) in
  // called when user pressed 'Return' or 'Command + D'
}

tableView.onFocus = { (current: NSIndexPath?, previous: NSIndexPath?) in
  // called as user navigates with arrow keys
}

We can use KBNavigationController to gain a Command + Left Arrow shortcut for going "back" (i.e. popping to the previous view controller in the stack).

Last but not least, KBTabBarController adds support for pressing Command + 1, Command + 2, Command + 3, etc. to change the currently selected tab. It supports up to 5 tabs. Neat!

More info about KBKit can be found at git.io/kbkit

Today we'll check out a few different tips and tricks for working with UITableView. Let's get started.

Adding a Refresh Control

We can add pull-to-refresh capabilities to any UITableView easily:

let refreshControl = UIRefreshControl()
refreshControl.addTarget(
  self, 
  action: Selector("refreshed:"), 
  forControlEvents: .ValueChanged
)

tableView.addSubview(refreshControl)

Then we just implement that refreshed: function like this:

func refreshed(sender: UIRefreshControl) {
  refreshTheThing {
    sender.endRefreshing()
  }
}

Reload Visible Cells

Here's a quick (almost) one-liner to reload only the visible cells in the table view:

tableView.reloadRowsAtIndexPaths(
  tableView.indexPathsForVisibleRows ?? [],
  withRowAnimation: .Automatic
)

Accessing Rects

It's often handy to grab a reference to the actual frame of a cell, section, header or footer. We can do this easily with:

tableView.rectForRowAtIndexPath(
  selectedIndexPath
)

Animate Height Changes

One easy trick to make our app look a little nicer is to animate changes to the heights of the UITableViewCells in our UITableView. All we need to do is make whatever changes to our models we want to cause our cells to be a different height. (For example we might let the user tap a button to "expand" all comments in an app). Then we just call:

tableView.beginUpdates()
tableView.endUpdates()

UIKit will interpolate & animate the changes, neat!

iOS gives us a ton of great capabilities to build upon. Being able to use the user's current geographic location in our apps really has changed the world.

We always need to ask for the user's permission first though, so we might as well do it in style! Today we'll check out a simple yet beautiful library from Sven Tiigi called STLocationRequest that uses Flyover to provide a great looking "location access prompt" view controller.

We'll begin by importing STLocationRequest, and configuring and showing a location request controller.

self.showLocationRequestController(
  setTitle: "Allow location access?", 
  setAllowButtonTitle: "Sure", 
  setNotNowButtonTitle: "Nope", 
  setMapViewAlphaValue: 0.7, 
  setBackgroundViewColor: .orangeColor()
)

Then, we can subscribe to the NSNotifications the library posts to know how the user responds.

More info about STLocationRequest can be found at git.io/stlocationrequest

Vector graphics are fantastic. They give us small, compressible files, crispy rendered pixels at any scale, and they're supported by many different kinds of software and frameworks.

Except iOS 😭, which doesn't support them out of the box. Today we'll try a library from Michael Choe called SwiftSVG which provides great support for parsing and rendering SVG files. Let's take a look.

SwiftSVG supports a ton of different ways of getting SVGs into our app.

CAShapeLayer(SVGURL: SVGURL)
UIView(SVGURL: SVGURL)
UIView(pathString: deathStarBlueprints)
CAShapeLayer(pathString: superSecretNewLogoIdea)
UIBezierPath(pathString: "M150 0 L75 200 L225 200 Z")

SwiftSVG also provides a custom UIView subclass we can use in Interface Builder that supports IBInspectable/IBDesignable.

More broadly, it's important to think about the reasons why we might want to use SVGs in our app. SVGs are essentially XML files (Open one up in a text editor, you'll see.)

They are instructions on how to draw a set of paths. The files are incredibly small.

Getting a good workflow going for creating them at-will can be tricky, but Sketch (eventually) makes it mostly painless.

Once we have this, we can build some awesome abstractions. Imagine a Swift struct with SVGName, color, and size that can produces a UIImage.

struct SVGImage {
  let SVGName: String
  let size: CGSize
  let color: UIColor

  func asImage() -> UIImage {
    // Neato!
  }
}

More info about SwiftSVG can be found at git.io/swiftsvg

It happens to the best of us. We're going along, composing some UIViews in our app. We're styling them, configuring them, we can't wait to try them out. That's when we see it. A big, empty, unescapable void of nothingness right where our views should be! 😱

Today we'll look at some tips to solve a case of missing UIViews.

First, we should arm ourselves with tools to help inspect what's going on. Xcode's own View Debugger is a great place to start, and Reveal.app is a fantastic third-party solution.

Whether it's one of these tools, or simply writing some in-depth logging functions, our first step should be to look at both our view hierarchy and its layout for anything funky.

print(UIApplication.sharedApplication().keyWindow?
  .performSelector("recursiveDescription"))

We can use private functions like recursiveDescription to print out a handy tree-view of our views to Xcode's Console.

Most of these debugging sessions stop here, as the problem often involves a view not being in the correct place in the hierarchy, or Auto Layout has gone and done something wacky with our views' locations.

If we're still stuck though, here are some sanity checks we can perform:

  • Are any views hidden?
  • How about transparent? (0 alpha)
  • Is it masked via a maskLayer?
  • Does it have a CGRectZero frame?
  • Is the key UIWindow a weird size or in a weird place?

Try making each view a specific ugly color (the static .XColor() functions on UIColor work wonders here).

  • Is another view covering it up?
  • Is .clipsToBounds enabled and a strange frame/bounds is causing the view to be clipped?

If all else fails, comment out everything, and add it back one bit at a time until something fails.

Happy 🐞 hunting!

Topics

#195: Mastering UIColor with Hue 🌈

Topics

Today we'll check out a library from Hyper called Hue. It gives us all kinds of helpful utilities for working with colors, let's dive in.

First, Hue helps us easily can parse colors from hex strings:

let beige = UIColor.hex("#FEFAF1")
let lightBrown = UIColor.hex("#AB9372")
let brown = UIColor.hex("#8C5637")

In addition, Hue can analyze colors:

beige.isDark // returns false

Hue can easily create gradients. Here we'll take a couple of colors, and create a CAGradientLayer from them:

let gradient = [lightBrown, brown].gradient { gradient in
  gradient.locations = [0.25, 1.0]
  return gradient
}

Finally, Hue can analyze a UIImage, returning a tuple of colors it finds inside.

let (bg, primary, secondary, _) = albumArt.colors()

It provides background, primary, secondary and detail colors from the image. We can the use these to create interfaces such as the one found in iTunes on OS X, where the colors in the UI match those found in the album artwork. Neat!

More info about Hue can be found at git.io/hue

Topics

#193: UIView Transition Basics 🐛

Topics

Most iOS developers have used the fantastic UIView animateWithDuration family of functions. But there's another, slightly-lesser known static function on UIView that can help us transition like a pro. Let's check it out:

The function we'll be trying is transitionWithView. At first glance you'll see it's takes the same duration, options, and closures as its more-popular sister function.

However, instead of applying our changes over time, this function will (conceptually) take a snapshot of our view before and after the work in the animations** closure** is performed, then visually transition from the first snapshot to the second.

func flip() {
  flipped = !flipped

  UIView.transitionWithView(
    button,
    duration: 0.3,
    options: .TransitionFlipFromTop,
    animations: {
      self.button.setTitle(self.flipped ? "👎🏻" : "👍🏻", forState: .Normal)
    },
    completion: nil
  )
}

The type of transition depends on the animation option we pass in. We can do everything from simple cross-dissolves (fades), to fancy 3D flips. Super handy for simple transitions.

Download a sample project at j.mp/bite193. In it, we flip a thumbs up emoji using this incredibly simple technique.

Topics

#192: Being a Good Low Power Mode Citizen 🔋

Topics

Low Power Mode debuted in iOS 9. It's a great way to get another hour or two of use out of a device.

When Apple first introduced this functionality, they touted how the feature turns of lots of little things that contribute to the battery savings.

It turns out we can actually provide these same savings in our own apps. Today we'll learn how. Let's get started:

The first way we'll interact with Low Power Mode is through a property on NSProcessInfo.

Here we're working on app that displays animated GIFs. When the user stops scrolling, we automatically begin looping the GIF. However, if the user has Low Power Mode enabled, we'll save a few CPU/GPU cycles by requiring the user to tap to start the animation:

func userStoppedScrolling() {
  guard NSProcessInfo.processInfo().lowPowerModeEnabled == false else { return }
  gifView.beginPlaying()
}

We'll finish up by registering for this NSNotification:

NSNotificationCenter.defaultCenter()
  .addObserver(self, 
    selector: "lowPowerModeChanged:", 
    name: NSProcessInfoPowerStateDidChangeNotification, 
    object: nil
  )

Then, inside lowPowerModeChanged, we can check with NSProcessInfo again. Now when a user returns to our app, we'll start/stop visible GIFs based on the Low Power Mode state. Neat!

Page 4 of 9