Active Filters: UIKit

Today we'll check out a library called Shoyu that aims to make setting up UITableViews a breeze. Let's dive in:

Shoyu works as a sort of DSL. It adds a source property on to UITableView, then gives as a few functions like createSection and createRow to fill the table view with content. Let's see what it takes to add a section and a row:

tableView.source = Source() { source in
  source.createSection { section in
    section.createRow { (row: Row<SpaceshipTableViewCell>) in
      row.reuseIdentifier = "SpaceshipCell"
      row.height = 52
      row.configureCell = { cell, _ in cell.nameLabel.text = "Tantive IV" }
    }
  }
}

We can use a similar technique to create and attach headers and footers to sections:

section.createHeader {
  $0.reuseIdentifier = "SpaceshipHeader"
  $0.height = 120
  $0.configureView = { view, _ in
    view.backgroundColor = .blackColor()
  }
}

There's also a few other functions to help us do things like dynamically set a row's height, or run some code when a row is selected:

section.createRow { row in      
  row.didSelect = { _ in /**/ }
  row.heightFor = { indexPath -> CGFloat? in
    return calculateHeight(indexPath)
  }
}

Last but not least, we can pass in an array of our model objects, then easily configure our cells with them:

section.createRows(spaceships) { (spaceship, row: Row<SpaceshipTableViewCell>) in
  row.height = 52
  row.configureCell = { cell, _ in cell.configure(spaceship) }
}

More info about Shoyu can be found at git.io/shoyu

Today we'll tackle a common question from those beginning to learn iOS development:

"UICollectionView and UITableView seem to overlap in terms of functionality in many ways. When should I use one or the other?"

The practical answer is actually a common idiom when building software: What's the simplest possible solution?

Let's look at the basics of table and collection views, then we'll circle back and learn how we apply that answer to our question.

UITableViews are all about vertically scrolling lists of content. They expect us to use them to display rows of content inside sections. We get a ton of conventional "iOS-native" feeling functionality for free such as group-style table views that look like iOS's settings app or system-standard accessory views.

In short: UITableViews give us a lot for free, but they expect us to think in their terms.

UICollectionViews are infinitely more customizable than Table Views. We can completely customize a collection view's layout, even animate between layouts.

UICollectionViews don't provide anywhere near as much functionality out of the box, but they offer an incredible amount of power and capability.

In many ways, Collection Views are the more-capable big sister to Table Views. They aren't constrained by "rows" or "accessory views". They are great for simple grids, all the way up to incredibly complex layouts.

Back to our original answer: What's the simplest possible solution?

We can now answer our question by rephrasing it: Given what I'd like to build, will a UITableView or a UICollectionView help me get there faster, with less (or at least simpler) code?

Building a Settings screen or vertical list of social media posts? A UITableView is probably the way to go.

Building a photo gallery, or perhaps a screen that needs to scroll content horizontally? We'll need a UICollectionView for those.

In either case, if we ever start to feel like we're fighting against UIKit, that's a good time to step back and re-evaluate.

It's quite common to need to display short status messages or notifications inside our apps. Today we'll look at a library called Whisper that is absolutely packed with features that make displaying these types of messages both simple and beautiful. Let's dive in.

Whisper provides 3 different types of display styles. All allow for extensive customization including colors, fonts, etc.

First up is "Whispering" a Message:

let message = Message(
  title: "🚨 Imperial Troops Have Entered the Base!",
  color: .redColor()
)

Whisper(message, to: navigationController, action: .Present)
Silent(navigationController, after: 3)

We call the Silent function right after we present the Whisper, this will dismiss the message after 3 seconds.

Next, let's try "Shouting" an Announcement.

This one's great for longer text, and looks great with an image. We can also pass in an closure to run when the user taps the banner.

let announcement = Announcement(
  title: "Han Solo Says:",
  subtitle: "Who's scruffy lookin'?!",
  image: UIImage(named: "avatar")
)

Shout(announcement, to: self)

Lastly, we can "Whistle" a Murmur to display a subtle, classy message in the status bar:

let murmur = Murmur(title: "The last transport... is away! 🚀", backgroundColor: murmurColor)
Whistle(murmur)

More info about Whisper can be found at git.io/whisper

Today we're looking at another great library from Hyper called Pages. It's essentially a convenience wrapper on top of UIPageViewController (previously covered in Bite #55). It makes working with paged view controllers a bit more friendly. Let's begin:

We'll start by composing a PagesController. This will be the view controller that contains the other view controllers:

let pagesVC = PagesController([shipsVC, stormtroopersVC])

pagesVC.enableSwipe = true
pagesVC.showBottomLine = false
pagesVC.showPageControl = false

We use a few properties to configure the look and feel and behavior we want, then we can use the view controller like normal.

Pages has some awesome convenience functions for programmatically scrolling to pages:

pagesVC.goTo(1) // go to page at index 1
pagesVC.next() // go forward 1 page
pagesVC.previous() // go back 1 page

We can also easily add view controller pages on the fly:

pagesVC.add([resistanceBasesVC, jediTemplesVC])

Last but not least, Pages is quite configurable offering properties for customizing not only the look and feel, but also behaviors such as whether or not changing view controllers affects the title shown in navigation bar of the navigation controller containing the pages.

More info about Pages can be found at git.io/pages

Topics

#154: Return of the UIKit B-sides! 📼

Topics

Today we're continuing our look at lesser-known UIKit features. Let's get started:

Fixing Unwanted Undo Prompts

Ever been testing an app and have an unwanted "Undo" prompt appear? This can be dynamically enabled or disabled with a property on UIApplication:

UIApplication.sharedApplication()
  .applicationSupportsShakeToEdit = false

Customizing Tab Bar Items Layout

We can use the UITabBar's itemPositioning property to control whether tab bar items use a "fill" or "centered" layout:

let tabBarController = UITabBarController()
tabBarController.tabBar.itemPositioning = .Centered

Significant Time Changes

The applicationSignificantTimeChange app delegate function lets us know when things like daylight savings time begin.

Refresh Control Labels

UIRefreshControl has an attributedTitle property we can use to show a nice little bit of text when a user pulls to refresh:

refreshControl.attributedTitle = attributedStringForRefreshControl()

In-App Dictionary View Controller

This one is awesome.

We can use a reference library view controller to display a nicely formatted screen depicting the definition of a term in our app. Neat!

let vc = UIReferenceLibraryViewController(term: "awesome")
showViewController(vc, sender: nil)

We're continuing our look at lesser-known UIKit functionality today with UITableView & UITableViewCell. Let's see what we can find:

Blur & Vibrancy Visual Effects

We can set UIVisualEffectView backgroundView, and a vibrancy separatorEffect so our table view really shines:

let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .Dark))

tableView.backgroundView = blurView
tableView.separatorEffect = UIVibrancyEffect(forBlurEffect: blurView.effect as! UIBlurEffect)

Row Actions

We can get those awesome "swipe-to-reveal" actions from Mail.app in our own table views. We just need to implement one delegate function and return an array of UITableViewRowActions.

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
  let deployAction = UITableViewRowAction(
    style: .Default,
    title: "Deploy"
  ) { (action, indexPath) in
    // TODO: Deploy the troop at this indexPath.row
  }

  return [deployAction]
}

Adjusting to State Transitions

We can override the willTransitionToState(state:) and didTransitionToState(state:) functions in our UITableViewCell subclasses to run code when the cell begins or finishes showing the edit control or delete confirmation:

class StormtrooperCell : UITableViewCell {
  override func didTransitionToState(state: UITableViewCellStateMask) {
    super.didTransitionToState(state)

    if state == .ShowingEditControlMask { print("began editing!") }
  }
}

Multiple Selection Background View

We can set a special background view on cells that will be shown only when we our table view supports multiple selection:

multipleSelectionBackgroundView = EditingView()

Topics

#151: UIView B-sides 📼

Topics

Today we'll begin looking at some lesser-known functionality inside UIKit. Some of these may be old news, but hopefully not all are.

We'll start with UIView. Let's flip it over and see what kind of b-side treasures we can find:

Subview Callbacks

We can override functions like didAddSubview(subview:) and willRemoveSubview(subview:) in our UIView subclasses. With this, we can take some action when subviews are added/removed.

class ContainerView : UIView {
  override func didAddSubview(subview: UIView) {
    super.didAddSubview(subview)

    subview.frame = calculateFrame(subview)
  }
}

Resigning First Responder

An oldie, but a goodie. We can use the endEditing(force:) function to ask (or force) a view or any text fields inside it to resign as the first responder:

view.endEditing(true)

Custom Layer Class

We can specify a custom CALayer subclass for our UIView subclasses by overriding the layerClass function. This is great if our view needs to use CATiledLayer to scroll lots of content, or when building a camera preview view:

class CameraPreviewView : UIView {
  override class func layerClass() -> AnyClass {
    return AVCaptureVideoPreviewLayer.self
  }
}

Easy Masking

We can "mask" a view using the alpha channel of another view by setting it to the maskView property:

view.backgroundColor = UIColor.redColor()
view.maskView = UIImageView(image: UIImage(named: "vader"))

Let's draw some strings! There's plenty of text-drawing capabilities in iOS and OS X, with full frameworks like Text Kit (or the lower-level Core Text) dedicated to the task. Today, we'll start looking at these capabilities by using Core Graphics and UIKit to draw a multiline string.

Sometimes we need to draw text ourselves, “manually”. This can be helpful when optimizing for scrolling performance, or when complete customization is necessary.

First we'll need a view to draw into, we'll make a new UIView, and override drawRect.

Next, we'll start implementing our drawRect function with a few variables. We'll choose a font, then setup a paragraph style to make our text multiline, with some tall lines.

We'll also setup some drawing options to tell the system we want multiline text:

let textFont = UIFont(name: "Avenir Next", size: 17.0)!
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineHeightMultiple = 1.2
paragraphStyle.lineBreakMode = .ByWordWrapping

let drawingOptions: NSStringDrawingOptions = [
  .UsesLineFragmentOrigin, .UsesFontLeading]

We'll save these off into a textAttributes dictionary. Next, we'll need to figure out what rectangle we'll be drawing our text into.

We'll use the boundingRectWithSize function to find out how tall it will be when constrained to the width of our view:

let textRect = (text as NSString)
  .boundingRectWithSize(
    CGSizeMake(bounds.size.width, CGFloat.max),
    options: drawingOptions,
    attributes: textAttributes,
    context: nil)

Then we'll just pass that calculated textRect into a call to drawWithRect:

(text as NSString).drawWithRect(textRect,
  options: drawingOptions,
  attributes: textAttributes,
  context: nil)

Attributed strings are a fantastic way to work with styled text on iOS and OS X. NSAttributedString is an incredibly full-featured API, but because of this, simple tasks sometimes require a fair amount of boilerplate code. Today we'll look at BonMot, a library from Raizlabs that simplifies the process of composing attributed strings.

BonMot takes the form of a set of chainable functions that we can use to compose attributed strings:

let fancyQuote = "Traveling through hyperspace ain't" +
                 "like dusting crops, farm boy.\n" +
                 " — Han Solo"

quoteLabel.attributedText = BONChain()
  .fontNameAndSize("AmericanTypewriter", 17.0)
  .lineHeightMultiple(1.8)
  .string(fancyQuote)
  .attributedString

BonMot will do all the heavy lifting of applying attributes and return a fully-formed NSAttributedString ready for use wherever we need.

We can use BonMot to concatenate multiple composed attributed strings:

let chain = BONChain()

chain.appendLink(BONChain().string("one fish"))
chain.appendLink(BONChain().string("two fish"), separator:commaSpace)
chain.appendLink(BONChain().string("red fish").textColor(UIColor.redColor()), separator:commaSpace)
chain.appendLink(BONChain().string("blue fish").textColor(UIColor.blueColor()), separator:commaSpace)

BonMot even supports the NSTextAttachment parts of the NSAttributedString API. We can generate an “image next to a label” like this:

chain.appendLink(BONChain().image(ackbar))
chain.appendLink(
  BONChain()
    .fontNameAndSize("Futura-MediumItalic", 17.0)
    .string("It's a trap!"), separator:" ")

We've only scratched the surface here, BonMot has a ton of features to offer. More info about BonMot can be found at git.io/bonmot

Interface Builder is awesome. Assembling our interfaces in it can be a great way to work. Sometimes though, we want to create custom views that we draw and manage ourselves in code. It'd be nice if these custom views could work just as well inside Interface Builder as they do in our code. Today we'll look at two awesome features that debuted with Xcode 6 called @IBInspectable and @IBDesignable. Let's dive in:

@IBInspectable var borderWidth: CGFloat = 0 {
  didSet { layer.borderWidth = borderWidth }
}

When crafting a custom view, we can make a property “inspectable” in Interface Builder by prefixing its definition with @IBInspectable. Now, when we drag an instance of our custom view onto the canvas in Interface Builder we'll be able to change our properties with native controls!

This technique even works with extensions! Tired of not being able to configure things like a UIButton's borders in IB? Let's add a tiny extension to UIButton to make it more inspectable:

extension UIButton {
  @IBInspectable var borderWidth: CGFloat {
    get { return layer.borderWidth }
    set { layer.borderWidth = borderWidth }
  }
}

This is great so far, but what we really want is to be able to see the effects of our property edits live in the IB canvas. We can prefix any view's definition with @IBDesignable to tell IB to render the view, live, right on the canvas.

@IBDesignable
class SpeedometerView : UIView {
  var pieChartView: PieChartView

  @IBInspectable var meterColor: UIColor = UIColor.yellowColor() {
    didSet { pieChartView.segmentColor = meterColor }
  }

  // ...

When we combine the two techniques we unlock a much more intuitive (and fast) way to build custom views. We can even implement a special function called prepareForInterfaceBuilder() on our view where we can configure it with example data that will be drawn only when it's rendered in IB.

If anything goes wrong, we can debug our view by setting a breakpoint like normal, then choosing Editor > Debug Selected Views from Xcode's menu.

Page 6 of 9