Cocoa Scientist

Passengr

Driving Information for the Cascade Mountains

Getting Started

Passengr is an iPhone app that displays driving information and road closures for the Cascade Mountain Passes in Washington State. This case study describes the process behind building Passengr. The data on pass closures, obtained from the WSDOT website, is displayed using collection views. We’ll create a custom animation to smoothly transition between views. Lastly, this app is an example of how property lists can be utilized to persist model objects.

When complete, the finished app will look like this.

There are four main topic areas in this case study. We’ll touch on a few other things, but here are the big ones:

  1. Navigation controllers and segues
  2. Collection views with custom cells and layouts
  3. Saving application data
  4. Building animations

Requirements

We can begin by stating up front what we’re trying to build:

The app should display high-level and detailed driving information for the Cascade Mountain Passes.

Next we’ll roughly identify the pieces of functionality we’ll need to build. If these descriptions sound fuzzy, that’s ok. We’re only thinking about the big picture here and the large pieces we might need.

We need something to provide a list of mountain passes. We can start by hard coding the list of pass names into the app– they aren’t changing any time soon. But there are quite a few passes, some major but most minor. Many are even closed during the winter months and only relevant in the summer. So we really need something to provide a list of the passes the user personally wants to see.

We are also going to need something to fetch the status of each pass so we can determine which are closed and which are open. This “something” will need to communicate over the network and download data from the WSDOT website. Pass information is available as HTML or XML data, so our app will need to handle one of those.

We also need to think about translating some piece of XML/HTML into a model representing a Pass. A pass will most definitely have a name. It also will need to show the road conditions status in eastbound and westbound directions. Sometimes only one direction of the pass is closed, so we need to account for this.

Then we’ll need some interface to display the pass information. Since there are one or more passes, we probably want a list of some kind, such as a table view or collection view.

That gives us a rough idea of what to build. Without worrying too much about the specifics right now, we can get started.

Jumping In

Create a new project in Xcode using the Single View Application template. Give the project a name (our case study will use Passengr) and save the project somewhere on your computer.

If you’re not in the habit of using git, it’s a good time to consider adding it to your workflow. Learning the basics is easy and the payoffs are well worth it. While version control tools exist for many reasons, a huge benefit, especially when starting off as a solo developer, is maintaining your sanity when things suddenly stop working.

With the project created, it’s time to dive in and start building.

Collection Views

UICollectionView was introduced in iOS 6 as a way to display ordered data, with very few restrictions on how the data can be laid out or arranged. It shares much of the same concepts as UITableView, such as using index paths to specify rows and reusing cells as they scroll off-screen, but a collection view has a much more general purpose.

For example, the cells in a collection view can be any size and they can scroll horizontally or vertically. The layout of the cells is completely determined by the collection view. The cells are not restricted to positioning themselves linearly, one after another, as with a table view. They can be laid out in any configuration. Think of iBooks, Photos, or Calendar– these are all examples of layouts you can create using UICollectionView.

Cells in a collection view are subclasses of UICollectionViewCell. Unlike table view cells, a collection view cell doesn’t come with anything for free. There are no predefined styles available with labels or images already attached. Anything you wish to show in a collection view cell needs to be added yourself.

Using a collection view requires a data source. The data source provides information about what is displayed by conforming to the UICollectionViewDataSource protocol. This protocol works much like the table view counterpart, so it should be familar already.

Adding a Collection View

We’ll start in the storyboard. With the single view application template, there is already a view controller on the canvas by default. We’ll be using a UICollectionViewController subclass, instead of UIViewController. This class comes configured for a collection view and is already wired up for data source and delegate methods.

First delete the default view controller already on the storyboard. Then find the Collection View Controller in the Object Library and drag it out onto the storyboard. The storyboard should look similar to the image below:

Collection View Controller on Storyboard

If you build and run the app now, you won’t see much. The collection view has a black background by default and the data source is returning zero cells, so there is nothing on screen.

In order to display cells, we need to create a subclass of UICollectionViewController. First, find and delete the default ViewController from the project navigator. Xcode created this view controller as part of the single application template, but we won’t need it anymore.

Delete ViewController.swift

Next, select File > New… > File or use the ⌘N shortcut and create a new Cocoa Touch subclass of UICollectionViewController. This view controller will display a list of passes, so use a descriptive name to identify the file later, such as PassViewController.

Open the new subclass and take a few moments to look over the template code provided. Note how a cell is registered with the collection view inside viewDidLoad. If a cell is not registered, the collection view will give an error at runtime. Cells can be registered from a XIB or custom subclass, but in this case the default UICollectionViewCell is used.

private let reuseIdentifier = "PassListCell"

class PassViewController: UICollectionViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Register cell classes
        self.collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
    }

    ...
}

Next look at how the UICollectionViewDataSource protocol is conformed to. There are three functions that are implemented. These functions take care of returning counts for sections and rows, and creating cells for each index path in the collection view. If you’re familiar with UITableViewDataSource, then you’ll notice the similarity between the two protocols.

It’s easy to modify the data source implementation and return some blank cells for the collection view. In the example below, the data source returns ten red cells.

class PassViewController: UICollectionViewController {
    
    ...

    // MARK: UICollectionViewDataSource

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        // One section in the Collection View
        return 1
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // Ten cells in the Collection View
        return 10
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
        
        cell.backgroundColor = UIColor.red
        
        return cell
    }
}

If you build and run the application now, you should see a collection view filled with ten red cells.

A Collection View with Cells

Creating a Cell Subclass

You may have noticed there is a warning in the project about prototype cells requiring reuse identifiers. This warning is coming from the default cell on the collection view inside the storyboard. To correct the warning, find the collection view in the storyboard, then select and delete the default cell. Don’t worry, the collection view will still contain cells because it registers for them inside viewDidLoad.

By itself, an empty UICollectionViewCell isn’t very useful. What we really want is a subclass that can be customized. To do so, create a new Cocoa Touch subclass of UICollectionViewCell called PassListCell. Make sure a XIB file is also created, so that the cell can be loaded from outside the storyboard.

Open the XIB file for the cell subclass and add a label, along with constraints to center it vertically and horizontally. It doesn’t matter what size the cell is because in the end the collection view layout will govern the cell size.

Collection View Cell with Centered Label

Open the assistant editor and add an outlet for the label. If you don’t wish to use the assistant editor, you can do this manually by declaring an @IBOutlet in code and wiring it up from the storyboard. One nice feature of the assistant editor is that it will take care of creating weak references correctly, something that’s easy to forget when declaring outlets manually.

Next open the PassViewController and change how the data source constructs a cell. Two changes need to be made. First, change the cell registration so that a cell is loaded from the new XIB file. When dequeueing cells, cast the cell to a PassListCell and set the title label text.

private let reuseIdentifier = String(describing: PassListCell.self)

class PassViewController: UICollectionViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let nib = UINib(nibName: String(describing: PassListCell.self), bundle: nil)
        self.collectionView?.register(nib, forCellWithReuseIdentifier: reuseIdentifier)
    }

    ...


    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
        
        if let cell = cell as? PassListCell {
            cell.titleLabel.text = String(indexPath.row)
            cell.backgroundColor = UIColor.white
        }
        
        return cell
    }
}

Build and run the app. You should see ten cells laid out in a grid, with each cell showing the index Path row. Change numberOfItemsInSection: to return more than ten cells and you’ll be able to scroll through the collection view. Because of cell reuse, UICollectionView is capable of handling a significant number of cells.

Collection View Layouts

A collection view layout defines how the cells are positioned relative to one another. So far we’ve been using the default layout associated with the UICollectionViewController from the storyboard. This layout is defined as a UICollectionViewFlowLayout with an itemSize of 50x50 points. This is why, regardless of how you size the cell subclass inside to XIB, it displays as a 50x50 square.

If the layout you wish to use is simple enough, you may be able to define all the attributes needed on the default layout inside interface builder. However, as the layout becomes more complex, you’ll most likely want to subclass on UICollectionViewFlowLayout or UICollectionViewLayout` to gain finer control of cell positioning.

final class ListViewLayout: UICollectionViewFlowLayout {
    override init() {
        super.init()
        
        self.minimumInteritemSpacing = 10.0
        self.minimumLineSpacing = 10.0
        self.scrollDirection = .vertical
        self.sectionInset = UIEdgeInsetsMake(8.0, 0.0, 0.0, 0.0)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

extension ListViewLayout {
    class func listLayoutItemSize(for bounds: CGRect) -> CGSize {
        let width = bounds.width - 30.0
        let height = CGFloat(75.0)
        
        return CGSize(width: width, height: height)
    }
}

You also need to implement to delegate method. This can be done as an extension.

extension PassViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        
        return ListViewLayout.listLayoutItemSize(for: UIScreen.main.bounds)
    }
}

Finally, hook up the new layout inside PassViewController.

class PassViewController: UICollectionViewController {

override func awakeFromNib() {
    super.awakeFromNib()

    self.collectionView?.collectionViewLayout = ListViewLayout()
}

}

Array as Data Source

Next we’ll create a simple array with a list of pass names as strings to act as a data source. Eventually we’ll be creating Pass model objects, but for now, during the very early stages, we just need some dummy data to get things started.

Open up PassViewController and declare a private array of String types. It doesn’t really matter what strings are used here, because (eventually) we’ll be replacing the array with a real data source. Below are some of the Cascade passes to use as sample data.

class PassViewController: UICollectionViewController {
    
    private var passes: [String] {
        return ["Blewett", "Cayuse", "Chinook", "Disautel", "Manastash", "Sherman", "Snoqualmie", "Stevens", "Wauconda", "White"]
    }

    ...
}

We also need to change the collection view data source implementation to use the passes array when returning cells. Because the passes array is declared as an array String type, we can set the text label directly.

class PassViewController: UICollectionViewController {

    ...

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return passes.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
        
        if let cell = cell as? PassListCell {
            cell.titleLabel.text = passes[indexPath.row]
            cell.backgroundColor = UIColor.white
        }
        
        return cell
    }
}

Build and run the app. You should see a list of collection view cells showing pass names and arranged using the custom layout.

Resources

See the Custom Collection View Layouts article in issue #3 of objc.io for more background on implementing custom layouts. Thia article on scroll views from the same issue is also helpful for understanding the concepts on the class behind UICollectionView.

The Apple documentation also provides a nice introduction to collection views via the UIKit User Interface Catalog.

Navigation Controllers

So far we’ve created a collection view and populated it with cells, each one showing the name of a pass. We’ve created a custom flow layout to position the cells as evenly spaced rectangular views. Next we’re going to add a navigation controller to the interface and embed the collection view within it.

Navigation controllers are at the heart of many iOS apps so it’s likely you’re already familiar with them.. Apps like Mail, Notes and Settings all use navigation controllers. A UINavigationController is a special type of view controller called a Container View Controller. A navigation controller manages a stack of child view controllers, and knows how to move back and forth between view controllers.

In our app, we’ll use a navigation controller to move back and forth between a view of all the passes and a view of one pass.

Add a Navigation Controller

When using a storyboard, the easiest way to add a UINavigationController is to select the initial view controller and choose the Editor > Embed In > Navigation Controller menu option. This will add a new navigation controller onto the storyboard, mark it as the initial view controller, and set its root view controller pointing to the embedded controller.

Each UIViewController has a title property. When a view controller is placed inside a navigation controller, this title property is used to set the title of the navigation bar. Set the view controller title inside viewDidLoad: for each view controller.

class PassViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "Passes"
    }
}

Run the app and you should see a title appear in the navigation controller. We can’t do much else as this point because there’s no second view controller to segue to.

Using Navigation Controllers Programmatically

It’s also possible to position a view controller without any storyboards. As an aside, let’s take a moment to look at how this is accomplished. While creating and launching an app in this fashion isn’t really practical, it does help to illustrate how the main interface is created.

Let’s look at a code example and then walk through it.

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        
        window = UIWindow(frame: UIScreen.main.bounds)
        
        let viewController = UIViewController()
        let navController = UINavigationController(rootViewController: viewController)
        
        window?.rootViewController = navController
        window?.makeKeyAndVisible()
        
        viewController.title = "Hello World"
        viewController.view.backgroundColor = UIColor.white
        
        return true
    }
}

The example above is from the AppDelegate of a very basic app. This app has one navigation controller containing one view controller. The view controller sets the title and background color.

Other Use Cases

It is sometimes helpful to use a navigation controller so that the root view controller has a place to display buttons. The navigation controller provides a navigation bar that can contain subviews, such as buttons or labels. Placing buttons in the top left and right corners of a view controller is an easy way to display Cancel or Done buttons. This might be done as part of a modal alert view.

Similarly, the navigation bar can be used to show the title of the current view controller. If the title property is set on a UIViewController, then it will be shown in the center label of the navigation bar.

Buttons can be added via the storyboard, which is what we’ll do later on, but it’s sometimes helpful to do so via a view controller subclass. Consider the following example.

class PassViewController: UICollectionViewController {

    ...

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = NSLocalizedString("Cascade Passes", comment: "Cascade Passes")

        let buttonTite = NSLocalizedString("Back", comment: "Back")
        self.navigationItem.backBarButtonItem = UIBarButtonItem(title: buttonTite, style: .plain, target: nil, action: nil)
    }
}

Here the view controller title is set to “Cascade Passes”. Because this title is a little long, the custom “Back” button title is used. This button title will be used when the detail view controller is pushed onto the stack.

Styling the Navigation Bar

It is common to change the style of the navigation bar to match the overall app theme. Just set the color and change the status bar style in the plist.

class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
        
        let barTintColor = UIColor(red: 35.0/255.0, green: 105.0/255.0, blue: 46.0/255.0, alpha: 1.0)
        UINavigationBar.appearance().barTintColor = barTintColor
        UINavigationBar.appearance().tintColor = UIColor.white
        
        let attributes = [NSForegroundColorAttributeName: UIColor.white]
        UINavigationBar.appearance().titleTextAttributes = attributes
    }

    ...
}

Resources

For more detail on using storyboards with iOS, see this two part tutorial on the Ray Wenderlich site.

The UINavigationController documentation is long and detailed but it provides some helpful illustrations of a user interface blown-out, with the layers of navigation elements exposed.

Segues

Segues define transitions from one view controller to another. They link two view controllers together and are typically created in the Storyboard.

If the storyboard is a floor map of your views, then segues are the pathways between. They define the flow of your app or how the user navigates from one screen to the next. Segues are triggered in response to user actions, such as a button tap or cell selection in a table.

Creating Detail View Controller

Before creating a segue, we need a new view controller to transition over to. Create a new UICollectionViewController subclass, much like we did previously with PassViewController. This new collection view controller will also contain stub data, because we’re still working to establish basic app flow.

We’ll also be creating a new UICollectionViewFlowLayout subclass to coincide with the detail view. This layout will scroll horizontally, with one cell taking up the full screen.

Luckily, we already have a collection cell subclass to work with. Because the PassListCell is configured using Auto Layout, we’ll be able to reuse it and change the itemSize on the collection view layout. This will give the cell a different look between to two view controllers, even though the XIB file used will be the same.

Here’s a sample implementation for the DetailViewController.

private let reuseIdentifier = String(describing: PassDetailCell.self)

class DetailViewController: UICollectionViewController {
    
    private var passes: [String] {
        return ["Blewett", "Cayuse", "Chinook", "Disautel", "Manastash", "Sherman", "Snoqualmie", "Stevens", "Wauconda", "White"]
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // TODO: use a custom detail cell

        let nib = UINib(nibName: String(describing: PassDetailCell.self), bundle: nil)
        self.collectionView!.register(nib, forCellWithReuseIdentifier: reuseIdentifier)
    }

    // MARK: UICollectionViewDataSource

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.passes.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
        
        if let cell = cell as? PassListCell {
            cell.titleLabel.text = passes[indexPath.row]
            cell.backgroundColor = UIColor.white
        }
        
        return cell
    }
}

Notice how the UICollectionViewDataSource protocol implementation is the same as before with the pass view controller. The numberOfItemsInSection is determined from the dummy list of passes and the cell title label is set to match the pass name. In viewDidLoad, the same PassListCell XIB is reused with this new collection view controller.

Next create the DetailLayout. This layout will have a horizontal scroll direction with an item size that mostly fills the screen. We’ll be revisting this layout later on, when the interface detail cell is created.

class DetailViewLayout: UICollectionViewFlowLayout {
    
    override init() {
        super.init()
        
        self.minimumInteritemSpacing = 0.0
        self.minimumLineSpacing = 30.0
        self.scrollDirection = .horizontal
        self.sectionInset = UIEdgeInsetsMake(8.0, 15.0, 0.0, 15.0)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

extension DetailViewLayout {
    class func detailLayoutItemSize(for bounds: CGRect) -> CGSize {
        let width = bounds.width - 30.0
        let height = bounds.height - 158.0
        
        return CGSize(width: width, height: height)
    }
}

The numbers used for frame calculations and such are mostly just personal preference. They were chosen to create cell that almost takes up the screen, creating a card-like layout.

Configure the DetailViewController so the collection view uses the new layout. A good place to do this is inside awakeFromNib since the collection view will be available at that time.

class DetailViewController: UICollectionViewController {

    override func awakeFromNib() {
        super.awakeFromNib()
        
        self.collectionView?.collectionViewLayout = DetailViewLayout()
        self.collectionView?.pagingEnabled = true
    }
}

Adding a Segue

With the view controller created in code, the next step is to wire things together using Interface Builder. This means adding an instance of the detail view controller onto the storyboard, creating a segue with the pass via the controller.

Open the storyboard and add a new collection view controller onto the canvas. In the identity inspector, change the class type to match the subclass created earlier, DetailViewController. Next add a segue between the pass view controller and the detail view controller. The segue will be triggered programmatically whenever a cell is selected.

Select the PassViewController collection view and control-drag to the DetailViewController. In the contextual popup menu that appears, select the Show segue type. Use the inspector to give the segue an identifier so we can reference it later on.

Return to the PassViewController class and implement prepareForSegue:sender:. This method provides an opportunity to configure a view controller before it is displayed. In other words, we’ll be able to set properties on the view controller and tell it what pass to display, before it shows up.

class PassViewController: UICollectionViewController {
	
	...

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ShowDetailSegueIdentifier" {
            guard let indexPath = collectionView?.indexPathsForSelectedItems?.first else { return }
            segue.destinationViewController.title = passes[indexPath.row]
        }
    }

    // MARK: UICollectionViewDelegate
    
    override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        self.performSegue(withIdentifier: "ShowDetailSegueIdentifier", sender: indexPath)
    }

    ...
}

There are a few different segue types besides Show. The following table describes the different types and when they are used.

Name Description
Show (Push) Displays new content by calling showViewController:sender: on the target view controller. Typically the content is presented modally, but some view controllers override the presentation methods to present content differently. For example, navigation controllers use the Show segue to push new view controllers onto the stack.
Show Detail (Replace) Displays new content by calling showDetailViewController:sender: on the target view controller. This segue should only be used with controllers that are embedded within a split view controller. When triggered, this segue will replace the controller content in the detail split view pane.
Present Modally Displays the new content of a view controller modally. Allows the presentation and transition style to be customized. Can be used to present a full screen model view, such as for a login screen.
Present as Popover Similar to Present Modally, but the target controller is presented inside a popover in a horizontally regular environment.

Consult the View Controller Programming Guide for iOS for more information on segues.

Segue Identifiers

In the statically typed world of Swift, segue identifiers can present some friction. Because they are assigned plain-text string values in the storyboard, they are prone to spelling errors and typos. These are errors the compiler cannot find.

For example, consider the following snippet to perform a segue:

self.performSegue(withIdentifier: "SegueToDetallView", sender: nil)

Because the identifier type is a String, any string is valid. The compiler cannot determine if a segue with the identifier “SegueToDetallView” exists. The onus is on us to make sure it does exist.

During the Swift in Practice session at WWDC 2015, Apple laid out some techniques for using enums to provide a type-safe way to define segue identifiers. The example below is extracted from that talk.

protocol SegueHandlerType {
    associatedtype SegueIdentifier: RawRepresentable
}

extension SegueHandlerType where Self: UIViewController, SegueIdentifier.RawValue == String {
    
    func preformSegue(with identifier: SegueIdentifier, sender: AnyObject?) {
        performSegue(withIdentifier: identifier.rawValue, sender: sender)
    }
    
    func segueIdentifier(for segue: UIStoryboardSegue) -> SegueIdentifier {
        guard
            let identifer = segue.identifier,
            let segueIdentifier = SegueIdentifier(rawValue: identifer)
        else {
            fatalError("segue identifier not found for segue: \(segue)")
        }
        
        return segueIdentifier
    }
}

First a SegueHandlerType protocol is created. The protocol defines a single associated type SegueIdentifier that must conform to the RawRepresentable protocol. Next, a constrained protocol extension on SegueHandlerType is created, where Self is constrained to be a UIViewController, and SegueIdentifier.RawValue to be a String. Constrained protocols allow extensions to specify some criteria the underlying type must meet.

To use the SegueHandlerType, a view controller can first declare a SegueIdentifier enum that is internal to the class.

class PassViewController: UICollectionViewController, SegueHandlerType {

    internal enum SegueIdentifier: String {
        case ShowDetailView = "ShowDetailView"
    }

    ...
}

The segue identifier can then be retrieved from the enums rawValue and passed directly to performSegueWithIdentifier.

class PassViewController: UICollectionViewController, SegueHandlerType {
    
    ...

    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let identifier = SegueIdentifier.ShowDetailView.rawValue
        self.performSegue(withIdentifier: identifier, sender: indexPath)
    }

    ...
}

Using the SegueHandlerType gives us a type-safe way to interact with segues and reduces the chances of typos. We only have to make sure the SegueIdentifier name is spelled correctly once.

In the end, the app should segue over to the detail view controller when a cell is selected and the collection view should both be using custom layouts.

Edit View Controller

Up until now we’ve used only one navigation controller in our app. It’s been set as our initial view controller and is at the top of our view controller stack, but this isn’t a requirement– navigation controllers can used anywhere. One helpful use case for navigation controllers is to simply show a title and provide an area for buttons. A view controller might not provide navigation per se, but it may need to show a descriptive title and a dismiss button.

In the following example, we’ll create an edit view controller that allows us to configure which passes are shown and in what order. The edit view controller will be placed inside a navigation controller and use the navigation bar buttons.

Begin by creating a new subclass of UITableViewController, called EditViewController or something similar. We’ll use the UITableViewController class to gain some boilerplate for free. Since we’re still working with dummy data, use the same passes array implementation as the other two view controllers. Wire up the data source methods to return a table cell showing the pass name.

private let reuseIdentifier = String(describing: PassEditCell.self)

class EditViewController: UITableViewController {
    
    private var passes: [String] {
        return ["Blewett", "Cayuse", "Chinook", "Disautel", "Manastash", "Sherman", "Snoqualmie", "Stevens", "Wauconda", "White"]
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
    }

    // MARK: - Table view data source

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.passes.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
        
        let pass = passes[indexPath.row]
        cell.textLabel?.text = pass
        
        return cell
    }
}

Next, we need to add a new view controller onto the storyboard so we can present the edit screen. We’ll use a flip transition to move the user into edit mode. By adding an Edit button in the navigation bar, we can attach a segue and present the edit view.

Begin by opening the storyboard and adding a new UIBarButtonItem to the navigation bar on the PassViewController. By using the Edit system item type, the button will be localized.

Next, add a new navigation controller onto the storyboard. The navigation controller comes with a rootViewController already attached. This view controller happens to be a UITableViewController subclass, so we can use it as our EditViewController by changing the class type to match.

Use the pinch gesture to zoom in and out on the storyboard, or use the `⌘+` and `⌘-` shortcuts. Changing the storyboard size helps when organizing view controllers on a larger canvas. In order to select a view controller, zoom in until the controller fills the frame.

In the sample above, note how a default UITableViewCell is registered inside viewDidLoad. When the table cell is dequeued later, the textLabel is set to match the pass name. The implementation is very basic, so let’s expand on it.

To start with, let’s add a switch to the table view cell so we can enable visibility for each pass. Create a new UITableViewCell subclass, along with a XIB file. Lay out a cell with one UILabel on the left and a UISwitch on the right, along with appropriate constraints. Add outlets for both the label and the switch.

Return to the EditViewController and change how the table view cell is registered. The view controller should use the PassEditCell instead of the default table cell.

private let reuseIdentifier = String(PassEditCell)

class EditViewController: UITableViewController {
    
    ...
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let nib = UINib(nibName: String(describing: PassEditCell.self), bundle: nil)
        self.tableView.register(nib, forCellReuseIdentifier: reuseIdentifier)
    }

    ...
}

Open the storyboard and drag out a navigation controller. The navigation controller comes with a UITableViewController attached as its root view controller. Select the attached view controller and change its class to match our EditViewController.

Create a segue between the edit button and the edit view controller. This is done by selecting the edit button and control dragging to the second navigation controller (containing the edit view controller). Select the Present-Modal segue type and the Flip-Vertical transition type.

At this point, we have a way to present the edit view controller, but no way to dismiss it. In order to handle this, add a Done button to the navigation bar, same as before with the Edit button.

Using the Assistant Editor, Control-Drag from the Done button to the EditViewController and add an @IBAction to dismiss the view controller. When the Done button is selected, call dismiss.

class EditViewController: UITableViewController {
    
    ...


    @IBAction internal func handleDoneButton(_ sender: AnyObject) {
        self.dismiss(animated: true, completion: nil)
    }
}

Build and run the app. You should be able to present the edit view controller and dismiss it. The controller should contain a list of pass cells, complete with names and switches.

Reordering Table View Cells

It would be nice to allow the order of the passes to be specified, so that the user can select the most important pass to display upfront. We can add support for sorting passes by allowing the edit table to be reordered. This way the user can sort the passes by relevance.

At the model level, this is something to add later when defining Pass model objects, but for now we can implement the required UITableViewDelegate methods so that our table can be reordered. The changes won’t be saved, but the UI will be in place.

Open the EditViewController and add the following UITableViewDelegate protocol methods. The table view also needs to be placed in edit mode by setting the isEditing flag to true. Alternatively, this could be done by a adding button that toggles to editing mode.

class EditViewController: UITableViewController {
    
    ...
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = NSLocalizedString("Edit Passes", comment: "Edit Passes")
        self.tableView.isEditing = true
        
        ...
    }

    ...

    override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        // TODO: reorder model objects
    }
    
    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
        return .none
    }
    
    override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
        return false
    }
}

The first method is straight forward. The delegate returns true in tableView:canMoveRowAt: so that each table cell can be reordered. This makes sense because we want the option to move every pass around.

The second method is empty for now. We’ll implement this later, when we have a real data model.

The last two are less clear. If you comment out editingStyleForRowAt: and shouldIndentWhileEditingRowAt:, you’ll find the edit table view still supports reordering, but the passes have a delete control on the cell. Since we don’t want to delete passes, implement tableView:editingStyleForRowAt: and return UITableViewCellEditingStyle.none. This removes the delete control, but the pass title label remains indented. To correct this, the method tableView:shouldIndentWhileEditingRowAt: is implemented to return false. The combination of these two methods gives the table cells a cleaner look.

Next we’ll add a data model and persistence layer. Once the data model is in place, we can return to the edit view controller and add functionality to allow the user to specify which passes are shown, and in what order.

Data Persistence

Data persistence is an important topic and a requirement for many mobile apps. Often an app will need to save some piece of data while allowing the user to manipulate and change it during use. Many times this data might be updated from a remote source and a local copy will need to be saved on the device.

In our app, the user might not want all the passes to appear all the time. For example, several passes are closed during the winter, so no one will be checking their status in the middle of January. Additionally, most drivers probably only care about the major highway passes, such as Snoqualmie. Using the edit view created earlier, we can allow the user to specify which passes are shown, and in what order. This information will be saved in Core Data, and we’ll use it to build the pass list.

There are many options for saving application data on iOS. We’ll look at a few solutions before finally focusing on Core Data.

NSUserDefaults

One of the simplest ways to store data is to utilize the user defaults system. NSUserDefaults can be thought of as a key-value store. There is support for storing a number of different object types, such as strings, numbers, dates, arrays, dictionaries, or raw NSData.

All iOS applications have their own user defaults database. This database is created when the app is first launched or any time the app is reinstalled.

Saving information in the user defaults is very easy. Simply grab an instance of the defaults and write your object using one of the set-for-key methods. Similarly, reading objects can be accomplished using the reverse.

let defaults = UserDefaults.standard
        
defaults.set("true", forKey: "isOwner")
defaults.set("Bob", forKey: "firstName")
defaults.set("3.14159", forKey: "magicNumber")

if let value = defaults.value(forKey: "firstName") as? String {
    print(value)
}

if defaults.bool(forKey: "isOwner") {
    print("is owner!")
}

This would actually be a fine solution for our app, because our data set is really small and simple. Using the setObject:forKey: method, would could assign an NSArray of custom objects to a key in the defaults system, provided the objects conformed to NSCoding.

NSCoding

The NSCoding provides a way for custom objects to serialize and deserialize themselves. This basically means the objects can be transformed into binary data and then reconstructed later on. Once transformed, the binary representation could be saved to disk to be reconstructed sometime later.

Objects that can be serialized can implement NSCoding. In our case, the model objects will implement the serialization protocol and be saved directly to disk.

Declare a subclass of NSObject to represent a pass and assign attributes for the pass data to model.

final class Pass: NSObject {
    let name: String
    let url: String
    
    var conditions: String = ""
    var eastbound: String = ""
    var westbound: String = ""
    
    var order: Int
    var enabled: Bool
    
    var lastModified: Date = Date()
    
    init(name: String, url: String, order: Int, enabled: Bool) {
        self.name = name
        self.url = url
        self.order = order
        self.enabled = enabled
        super.init()
    }
}

Change the Pass object so it conforms to the NSCoding protocol by implementing the initWithCoder: and encodeWithCoder: methods. The initWithCoder: method should assign property values that are pulled from the coder, using String key values.

extension Pass: NSCoding {
    convenience init?(coder aDecoder: NSCoder) {
        guard
            let name = aDecoder.decodeObject(forKey: "name") as? String,
            let url = aDecoder.decodeObject(forKey: "url") as? String
            else { return nil }
        
        let enabled = aDecoder.decodeBool(forKey: "enabled")
        let order = aDecoder.decodeInteger(forKey: "order")
        
        self.init(name: name, url: url, order: order, enabled: enabled)
        
        let conditions = aDecoder.decodeObject(forKey: "conditions") as? String ?? ""
        let westbound = aDecoder.decodeObject(forKey: "westbound") as? String ?? ""
        let eastbound = aDecoder.decodeObject(forKey: "eastbound") as? String ?? ""
        let lastModified = aDecoder.decodeObject(forKey: "lastModified") as? Date ?? Date()
        
        self.eastbound = eastbound
        self.westbound = westbound
        self.conditions = conditions
        self.lastModified = lastModified
    }

}

Similarly, the encodeWithCoder: should encode property values into the coder, and assign them key values.

extension Pass: NSCoding {
    ...

    
    func encode(with coder: NSCoder) {
        coder.encode(name, forKey: "name")
        coder.encode(url, forKey: "url")
        coder.encode(enabled, forKey: "enabled")
        coder.encode(order, forKey: "order")
        coder.encode(conditions, forKey: "conditions")
        coder.encode(westbound, forKey: "westbound")
        coder.encode(eastbound, forKey: "eastbound")
        coder.encode(lastModified, forKey: "lastModified")
    }
}

Creating a Data Source

Next we will create a data source object. The data source will provide a list of passes and will create the initial model. When the app launches for the first time, the model will be empty, and the data source will initialize the model with seed data. On subsequent launches, the data source will save and reload the existing model.

Add a new NSObject subclass named PassDataSource to the project.

class PassDataSource: NSObject {
    
    private(set) var passes: [Pass] = []

    override init() {
        super.init()
        
        loadOrCreateInitialModel()
    }

    func loadOrCreateInitialModel() {
        // TODO: implement
    }
}

All the data source does for now is declare an empty array of Pass objects. Notice the passes array has a private setter and is only exposed through a public getter.

Next we’ll implement loadOrCreateInitialModel that creates an empty model or loads an existing one. An empty model is a collection of pass entities that only have a name and url, but no driving conditions yet.

class PassDataSource: NSObject {

    ...

    fileprivate func loadOrCreateInitialModel() {
        if FileManager.default.fileExists(atPath: modelURL.path) == false {
            createInitialModelAtURL(url: modelURL)
        }
        
        let data = try! Data(contentsOf: modelURL)
        
        guard let passes = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Pass] else {
            fatalError("could not load passes, wrong data type")
        }
        
        assert(passes.count > 0, "passes should not be zero")
        
        self.passes = passes
        
        self.refreshFromRemoteData()
    }

    fileprivate var modelURL: URL {
        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return urls[urls.count - 1].appendingPathComponent("passengr.plist")
    }
}

Next we need to create the initial model by implementing createInitialModelAtURL. This is done by using a dictionary of pass names and urls as seed data. This data could come from a plist in your app resources instead of being hard-coded, but this works just as well.

class PassDataSource: NSObject {

    ...

    fileprivate func createInitialModelAtURL(url: URL) {
        var order = 0
        var passes: [Pass] = []
        
        for info in seedData {
            let url = info.path
            let name = info.name
            let pass = Pass(name: name, url: url, order: order, enabled: true)
            
            passes.append(pass)
            order += 1
        }
        
        if didWrite(passes: passes, to: modelURL) == false {
            fatalError("cannot saved model to url: \(modelURL)")
        }
    }
    
    fileprivate func didWrite(passes: [Pass], to url: URL) -> Bool {
        do {
            let data = NSKeyedArchiver.archivedData(withRootObject: passes)
            try data.write(to: url, options: .atomicWrite)
            return true
        } catch {
            return false
        }
    }
    
    fileprivate lazy var seedDictionary: [String: String] = {
        return [
            "Blewett": "http://www.wsdot.com/traffic/passes/blewett/",
            "Manastash": "http://www.wsdot.com/traffic/passes/manastash/",
            "Snoqualmie": "http://www.wsdot.com/traffic/passes/snoqualmie/",
            "Satus": "http://www.wsdot.com/traffic/passes/satus/",
            "Stevens": "http://www.wsdot.com/traffic/passes/stevens/",
            "White": "http://www.wsdot.com/traffic/passes/white/"
        ]
    }()
}

In the createInitialModel function, the dictionary keys are first sorted alphabetically. This ensures the initial ordering matches the alphabetical ordering. Next, the keys are iterated over, and a new Pass entity is created for each key. The private lazily generated seedDictionary property contains the initial data to create pass entities from.

Note the use of NSKeyedArchiver to convert the passes array into NSData and then write the data to disk using writeToURL. We can use NSKeyedArchiver with Pass objects because we’ve implemented the NSCoding protocol. The Pass object provides a way to represent or serialize itself using the encodeWithCoder: function.

Using the Data Source

To use the data source, we need to decide what object shall instantiate and own it, and how it will be accessed throughout our app. One option is the make the data source a singleton and use a sharedInstance type class function to access it where needed. Another option is to create the data source once, and pass it along to the objects that need it– this is the option we’ll choose.

In our case, we can use the AppDelegate to instantiate and own the data source. Inside application:didFinishLaunchingWithOptions:, we can pass the data source along to the PassViewController, which in our app is the top-level view controller. From there, it is up to the pass view controller to give the data source to whatever objects need it.

Open up the AppDelegate and add a private property for the data source. The property can be initialized right when it is declared. Next change application:didFinishLaunchingWithOptions: to first find an instance of PassViewController and then set the data source property.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    
    private let dataSource = PassDataSource()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        guard let navController = self.window?.rootViewController as? UINavigationController else { return false }
        guard let viewController = navController.childViewControllers.first as? PassViewController else { return false }
        viewController.dataSource = dataSource
        
        return true
    }

    ...
}

To make everything compile, declare a dataSource property on the PassViewController. To use the data source, change the passes property to return the visiblePasses.

class PassViewController: UICollectionViewController {
    
    var dataSource: PassDataSource?

    fileprivate var passes: [Pass] {
        guard let dataSource = dataSource else {
            fatalError("data source is missing")
        }
        
        return dataSource.visiblePasses
    }

    ...
}

That takes care of the pass view controller, but what about the other two view controllers? We still have the detail view controller and the edit view controller. How will these view controllers access the data source?

The answer is the use the prepareForSegue:sender: method and pass the data source along to the next view controller.

class PassViewController: UICollectionViewController {

    ...

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let identifier = SegueIdentifier(rawValue: segue.identifier!) else { return }
        
        switch identifier {
        case .ShowDetailView:
            guard let viewController = segue.destination as? DetailViewController else { return }
            guard let indexPath = collectionView?.indexPathsForSelectedItems?.first else { return }
            
            viewController.indexPath = indexPath
            viewController.dataSource = dataSource
        case .ShowEditView:
            guard let navController = segue.destination as? UINavigationController else { return }
            guard let viewController = navController.childViewControllers.first as? EditViewController else { return }
            
            viewController.dataSource = dataSource
        }
    }

    ...
}

Before the prepareForSegue: changes will compile, a dataSource property needs to be added to both EditViewController and DetailViewController.

First change the detail view controller.

class DetailViewController: UICollectionViewController {
    
    var dataSource: PassDataSource?
    
    fileprivate var passes: [Pass] {
        guard let dataSource = dataSource else {
            fatalError("data source is missing")
        }
        
        return dataSource.visiblePasses
    }

    ...
}

Followed by the edit view controller.

class EditViewController: UITableViewController {
    
    var dataSource: PassDataSource?
    
    fileprivate var passes: [Pass] {
        guard let dataSource = dataSource else {
            fatalError("data source is missing")
        }
        
        return dataSource.orderedPasses
    }

    ...
}

Filtering Passes

Right now the data source provides an ordered list of all the passes, but that’s not all we need. In the app, the collection view will display an ordered list of the enabled passes, while the edit view will display an ordered list of all passes. To accomplish this, we can add two computed properties on the data source to return visiblePasses and orderedPasses.

class PassDataSource: NSObject {
    
    ...

    var orderedPasses: [Pass] {
        return self.passes.sorted(by: { (first, second) -> Bool in
            return Int(first.order) < Int(second.order)
        })
    }
    
    var visiblePasses: [Pass] {
        return self.orderedPasses.filter { $0.enabled == true }
    }

    ...
}

Above, the data source uses two computed properties to sort and filter the passes. Because the total list of passes is so small (there’s only six), it’s not very expensive to perform these operations on the fly. If the collection was significantly larger, then it’s likely we’d want to perform additional queries using NSPredicate to return the exact entities we want. Because the data is small, we can get away with not doing this.

Now that the above properties are defined, we can change the implementation of our view controllers to display the collections we want. Previously we stubbed out a dummy passes collection, but now we can query the data source directly.

Updating Objects

Now that the data source is in place and the view controllers have been configured to show passes, we can return to the edit view controller and finish adding the edit functionality. When we previously left off, the edit view controller could reorder passes and set pass visibility, but the changes weren’t saved.

The next steps are to change the edit view controller so that changes are saved when the “Done” button is pressed. The other two view controllers should reflect the changes when the edit view is dismissed.

Open EditViewController and modify the tableView:cellForRowAtIndexPath: method. The switch on the table view cell should be set to on or off based on the value of the enabled pass attribute.

class EditViewController: UITableViewController {

    ...

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
        
        configure(cell: cell, for: indexPath)
        
        return cell
    }

    fileprivate func configure(cell: UITableViewCell, for indexPath: IndexPath) {
        guard let cell = cell as? PassEditCell else { return }
        
        let pass = passes[indexPath.row]
        cell.titleLabel.text = pass.name
        cell.swtch.tag = indexPath.row
        cell.swtch.isOn = pass.enabled
        
        let action = #selector(EditViewController.handleSwitchChange(_:))
        cell.swtch.removeTarget(self, action: action, for: .touchUpInside)
        cell.swtch.addTarget(self, action: action, for: .touchUpInside)
    }
}

When the cell is returned, the switch is set from the isEnabled attribute of the pass. Additionally, the tag property of the switch is set to match the row of the index path. This is done so that when the switch is tapped later on, the handleSwitchChange: can determine what row was tapped, by looking at the tag of the sender.

class EditViewController: UITableViewController {

    ...

    @IBAction internal func handleSwitchChange(_ sender: AnyObject) {
        guard let swtch = sender as? UISwitch else { return }
        let pass = self.passes[swtch.tag]
        
        pass.isEnabled = swtch.isOn
        
        dataSource?.saveDataStore()
    }
}

In handleSwitchChange:, first the sender is checked to make sure it is a UISwitch. If not, we return immediately. Once we have a UISwitch, we find the associated pass using the tag property. This property was set to match the cell indexPath.row earlier. With a Pass object corresponding to the switch, we then flip the enabled bit to change the pass state and save the data source.

Saving the data source is a matter of converting the passes array into NSData and writing it to disk. This is accomplished inside the saveDataStore function. We’ve already implemented the private modelURL and didWritePasses:toURL function, and we can use these inside saveDataStore.

class PassDataSource: NSObject {
    
    ...

    func saveDataStore() {
        if didWrite(passes: passes, to: modelURL) == false {
            print("error saving passes to url: \(modelURL)")
        }
    }

    fileprivate var modelURL: URL {
        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return urls[urls.count - 1].appendingPathComponent("passengr.plist")
    }
    
    fileprivate func didWrite(passes: [Pass], to url: URL) -> Bool {
        do {
            let data = NSKeyedArchiver.archivedData(withRootObject: passes)
            try data.write(to: url, options: .atomicWrite)
            return true
        } catch {
            return false
        }
    }
}

When the save is successful, the data changes need to be propagated to UI– we’ll look at this a little later on.

To support the reordering of passes, we can return to the EditViewController and finish implementing the tableView:moveRowAt:to delegate function. This function first moves the Pass object from one index path to another, and then makes one iteration through the passes and updates the order property for each pass. This ensures the order of the passes reflects the order shown in the table view.

class EditViewController: UITableViewController {

    ...

    override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        var passes = self.passes
        
        let pass = passes.remove(at: sourceIndexPath.row)
        passes.insert(pass, at: destinationIndexPath.row)
        
        var order = 0
        for pass in passes {
            pass.order = order
            order += 1
        }
    }
}

Other Avenues

There is much more to the world of data persistence than we’ve covered here. Notably absent from the discussion is Core Data, a persistence framework provided by Apple that is available throughout the ecosystem. We’ve also only looked at persisting data locally, but your app may need to communicate with a remote server to store, update, and retrieve data.

Resources

In the Start Developing iOS Apps series, a meal tracking app is built over the course of several lessons. One lesson is devoted to Persisting Data using NSCoding and contains a great example of using the protocol to persist a simple data model to disk.

NSHipster has an article on using NSCoding and NSKeyedArchiver that provides a comparison between between Core Data and NSKeyedArchiver.

HTML Parsing

There are several 3rd-party libraries available for HTML parsing on iOS, but there are also two alternatives available by default: NSXMLParser and libxml2. Because HTML is closely related to XML, we can parse HTML content using any XML parser.

Apple provides NSXMLParser as part of the Foundation framework and libxml2 is an open source parser written in C. In our case, we’ll use libxml2 .

NSXMLParser is considered a stream based parser where as libxml is referred to as a DOM based parser. The difference between a SAX and a DOM parser is that with SAX, events a generated as the parser encounters tags in the document. With a DOM parser, the entire document is loaded up front, and a query tree is built. A DOM parser is generally easier to use, but there is some additional overhead to create the tree. A SAX parser is more efficient with larger documents– no tree is created (the document is parsed only once).

The other benefit to using a DOM parser is that we can query the document directly, because we’ve already seen the document once and built up a tree document representing it. With a SAX parser, we’re receiving callbacks as elements are encountered in the document. As a result, we cannot ask questions about the document as a whole, but instead must reason about it while reading the elements.

Bridging Header

In order to use libxml2, we must create a bridging header and import the required header files. This is because libxml2 is a C library and the bridging header is required to provide the links between the C and Swift world.

To create a bridging header, add a new header file to your project with a name prefix matching your target name. For example, because our target is named Passengr, the bridging header should be named Passengr-Bridging-Header.h. Next, use the Build Settings tab under the Project Settings, specify the Objective-C Bridging Header underneath the Swift Compiler - Code Generation section.

Sometimes it's easier to create a bridging header by adding some Objective-C code to the project. This will prompt Xcode to create a bridging header for you and configure the project settings. Afterwards, the Objective-C code can be removed from the project.

With the bridging header created, include the necessary header files.

#include <libxml/HTMLtree.h>
#include <libxml/HTMLparser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/xmlerror.h>

Xcode won’t be able to find those header until you tell it where to look. This requires changing the Header Search Paths under the Build Settings and adding an entry for $(SDKROOT)/usr/include/libxml2. Xcode will then search this directory for any included header files at build time.

Additionally, Xcode needs to be told where to look for the libxml library in order to link against it. Underneath Other Linker Flags, add an entry for -lxml2.

Build the project to verify everything compiles.

Wrapping libxml2

There are two types defined in libxml that we will be wrapping in Swift. These are the xmlDoc and xmlNode types. Because the documents we’ll be interacting with are HTML documents, instead of using xmlDoc directly, we’ll use an htmlDoc.

The xmlDoc type represents an XML document tree loaded by libxml. The xmlNode type represents an individual node in the tree. Each xmlDoc has one root xmlNode at the top, which may be retrieved by using xmlDocGetRootElement and supplying a pointer to the xmlDoc.

Here is some C code that uses libxml to load an XML document. Understanding the C code isn’t really the focus. Instead, we’re interested in how the library is used and what functions are called. Below we can see that in the main function, an xmlDoc is loaded from a filename using xmlParseFile, and an xmlNode is created using a call to xmlDocGetRootElement. And we can see some code to walk a chain of xmlNodes using the next pointer. These are some of the function calls and ideas we’ll look to mimic in Swift.

// gcc nodes.c -I/usr/include/libxml2 -lxml2 -o nodes

#include <stdio.h>
#include <string.h>
#include <libxml/parser.h>

const char * content = "<people><person>Bob</person><person>Alice</person></people>";

static void print_nodes(xmlNode *node) {
    xmlNode *current = NULL;

    for (current = node; current; current = current->next) {
        if (current->type == XML_ELEMENT_NODE) {
            xmlChar *content = xmlNodeGetContent(current);
            printf("name: %s, value: %s\n", current->name, content);
        }

        print_nodes(current->children);
    }
}

int main(int argc, char **argv) {
    xmlDoc *doc = NULL;
    xmlNode *root = NULL;

    doc = xmlReadMemory(content, strlen(content), "", NULL, 0);

    if (doc == NULL) {
        printf("error: could not parse content: %s\n", content);
        xmlFreeDoc(doc);
        return -1;
    }

    root = xmlDocGetRootElement(doc);

    print_nodes(root);

    xmlFreeDoc(doc);
    xmlCleanupParser();

    return 0;
}

Executing the code above generates the following output.

$ gcc nodes.c -I/usr/include/libxml2 -lxml2 -o nodes
$ ./nodes 
name: people, value: BobAlice
name: person, value: Bob
name: person, value: Alice

Several more examples of C code for interacting with libxml can be found in the documentation.

Structure of XML

Let’s take a moment and step aside to discuss the structure of an XML document. Consider the following chunk of XML of as an example.

<?xml version="1.0" encoding="UTF-8"?>
<people>
   <person id="101">Bob</person>
   <person id="102">Alice</person>
</people>

Above is a snippet of XML similar to what was shown in the C example. It includes the <?xml> prefix tag with the version and encoding, and the <person> elements include an id attribute. Information within a tag, such as the version, encoding or id above, are considered attributes.

The people node is considered to be the root node. In this example, the root people node has two children. The children both are leaf nodes, because they themselves have no children.

Modeling XML Documents

Returning now to Swift, create an XMLNode class with a xmlNode and xmlNodePtr type properties. Because we’re interacting with C objects, a class is used to remind us that we’re dealing with value semantics and we need to free resources when we’re finished.

class XMLNode {
    private let nodePtr: xmlNodePtr
    private let node: xmlNode
    
    init(node nodePtr: xmlNodePtr) {
        self.nodePtr = nodePtr
        self.node = nodePtr.pointee
    }
}

The xmlNode and xmlNodePtr types are coming from libxml. If the project won’t compile with those references, make sure to include the headers and link against libxml.

Next create an XMLDoc class. This class maintains a reference to an xmlDocPtr and is initialized using NSData, which is then passed along to the xmlReadMemory function.

class XMLDoc {
    final let documentPtr: xmlDocPtr
    
    init(data: NSData) {
        let cfEncoding = CFStringConvertNSStringEncodingToEncoding(String.Encoding.utf8.rawValue)
        let cfEncodingAsString = CFStringConvertEncodingToIANACharSetName(cfEncoding)
        let cEncoding = CFStringGetCStringPtr(cfEncodingAsString, 0)
        
        let bytes = data.bytes.assumingMemoryBound(to: Int8.self)
        self.documentPtr = xmlReadMemory(bytes, CInt(data.length), nil, cEncoding, 0)
    }
    
    deinit {
        xmlFreeDoc(documentPtr)
        self._root = nil
    }
}

To access the root node of an xml document, we can create a lazy root variable that is backed by a private weak variable. The weak variable is used because a strong reference to the root node is not required; the xmlDocPtr already maintains one.

class XMLDoc {
    
    ...

    private weak var _root: XMLNode?
    lazy var root: XMLNode? = {
        if let root = self._root {
            return root
        } else {
            guard let root = xmlDocGetRootElement(documentPtr) else { return nil }
            let node = XMLNode(node: root)
            self._root = node
            return node
        }
    }()
}

To find the content of a node, we can again use a lazily created property to return the node content as a String. The call to xmlNodeGetContent returns a pointer to an xmlChar * which is then converted as a C string.

class XMLNode {
    
    ...

    lazy var nodeValue: String = {
        let document = self.nodePtr.pointee.doc
        let children = self.nodePtr.pointee.children
        guard let textValue = xmlNodeListGetString(document, children, 1) else { return "" }
        defer { free(textValue) }
        
        let value = textValue.withMemoryRebound(to: CChar.self, capacity: 1) {
            return String(validatingUTF8: $0)
        }
        
        guard value != nil else { return  "" }
        
        return value!
    }()
}

This provides us some basic abstractions for interacting with XML.

func printXMLRoot() -> {
    let string = "<people><person>Bob</person><person>Alice</person></people>"
    guard let data = string.dataUsingEncoding(NSUTF8StringEncoding) else { return }
    let doc = XMLDoc(data: data)
    guard let root = doc.root else { return }

    print(root.nodeValue)
}

Handling HTML

Compared to XML, dealing with HTML requires a little more work. The structure of the document and rules for parsing are different. XML has very strict rules and requires every document to be well-formed. In constrast, HTML is much more lenient and will typically do everything it can to render a document.

When it comes to libxml and HTML, we need to specify some parsing options when reading the document into memory. These options are passed in as a bitmask of the htmlParserOption type. For our case, we’ll include HTML_PARSE_RECOVER for relaxed parsing, combined with HTML_PARSE_NOERROR and HTML_PARSE_NOWARNING to suppress errors and warnings.

It also makes sense to refactor our code a little to reflect that we’re working with HTML documents, and not XML. Rename the XMLNode class to HTMLNode and similarly, rename XMLDoc to HTMLDoc. Instead of using an xmlDocPtr to represent the document, the HTMLDoc class should use an htmlDocPtr– which is really just a typedef for xmlDocPtr.

Change the HTMLDoc initializer so that HTML parsing options are specified.

final class HTMLDoc {
    final let documentPtr: htmlDocPtr
    
    init(data: NSData) {
        let cfEncoding = CFStringConvertNSStringEncodingToEncoding(String.Encoding.utf8.rawValue)
        let cfEncodingAsString = CFStringConvertEncodingToIANACharSetName(cfEncoding)
        let cEncoding = CFStringGetCStringPtr(cfEncodingAsString, 0)
        
        // HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING
        let htmlParseOptions: CInt = 1 << 0 | 1 << 5 | 1 << 6
        
        let bytes = data.bytes.assumingMemoryBound(to: Int8.self)
        self.documentPtr = htmlReadMemory(bytes, CInt(data.length), nil, cEncoding, htmlParseOptions)
    }

    ...
}

Change HTMLNode to return the nodeValue as a String by using the xmlNodeListGetString function.

final class HTMLNode {
    private let nodePtr: xmlNodePtr
    private let node: xmlNode
    
    init(node nodePtr: xmlNodePtr) {
        self.nodePtr = nodePtr
        self.node = nodePtr.pointee
    }
    
    lazy var nodeValue: String = {
        let document = self.nodePtr.pointee.doc
        let children = self.nodePtr.pointee.children
        guard let textValue = xmlNodeListGetString(document, children, 1) else { return "" }
        defer { free(textValue) }
        
        let value = textValue.withMemoryRebound(to: CChar.self, capacity: 1) {
            return String(validatingUTF8: $0)
        }
        
        guard value != nil else { return  "" }
        
        return value!
    }()
}

The HTML document should now be parsed and loaded into memory. Next we’ll look at how the document can be queried in order to access specific nodes.

HTML and XPaths

XPath is a query language that allows nodes to be selected from an XML document. It can also be used against HTML documents. We’ll be able to use it to find specific pieces of a web page by referencing the DOM element id.

Begin by opening a page for one of the mountain passes from the WSDOT website. Using a browser with developer tools installed will make things easier. With the page open, we can see there is a section on the left for cameras and a section on the right for the pass report. By right-clicking on the pass report, we can inspect this section of the page in web developer tools.

Information about the pass is in a div element with the id of PassPageBoxPanel. If we dig into this div and look at some of the other elements, we’ll find they all have ids. These ids can be used to reference an HTML element by an XPath.

In order to use XPaths in our app, we need to add an xpath function to HTMLNode and use the xmlXPathEvalExpression API. This new function should accept an XPath in the form of a String and return an array of HTMLNodes that match the query.

class HTMLNode {

    ...
    
    func evaluate(xpath: String) -> [HTMLNode] {
        let document = nodePtr.pointee.doc
        guard let context = xmlXPathNewContext(document) else { return [] }
        defer { xmlXPathFreeContext(context) }
        
        context.pointee.node = nodePtr
        
        guard let result = xmlXPathEvalExpression(xpath, context) else { return [] }
        defer { xmlXPathFreeObject(result) }
        
        guard let nodeSet = result.pointee.nodesetval else { return [] }
        if nodeSet.pointee.nodeNr == 0 || nodeSet.pointee.nodeTab == nil {
            return []
        }
        
        let size = Int(nodeSet.pointee.nodeNr)
        let nodes: [HTMLNode] = [Int](0..<size).flatMap {
            guard let node = nodeSet.pointee.nodeTab[$0] else { return nil }
            return HTMLNode(node: node)
        }
        
        return nodes
    } 
}

First a context is created by calling xmlXPathNewContext and passing in a reference to the document. Next we check the context to make sure it’s not nil, and return an empty array if it is. Because context points to a C object, we cannot use the guard statement here; we must check for nil directly. If the context is valid, we can evaluate the query using xmlXPathEvalExpression and obtain a result.

Note how the defer statement is used above to free the objects returned from the C APIs.

Converting HTML

To help the conversion between HTML and model objects, we’re going to create a PassInfo typealias to a [String: String] dictionary. This will allow us to loosely model the pass information from the HTML. We’ll also create a data exchanging protocol that our Pass object will conform to.

typealias PassInfo = [String: String]

protocol PassInfoType {
    var passInfo: PassInfo { get }
    func update(using info: PassInfo)
}

The PassInfoType protocol contains a read-only getter for some PassInfo, and an updateUsingPassInfo function that is used to update the model object. Next change the Pass model object to conform to the PassInfoType protocol.

extension Pass: PassInfoType {
    var passInfo: PassInfo {
        return [
            PassInfoKeys.Title: self.name,
            PassInfoKeys.ReferenceURL: self.url
        ]
    }
    
    func update(using info: [String: String]) {
        self.conditions = info[PassInfoKeys.Conditions] ?? ""
        self.westbound = info[PassInfoKeys.Westbound] ?? ""
        self.eastbound = info[PassInfoKeys.Eastbound] ?? ""
    }
}

The passInfo dictionary returned contains only the value needed to identify a pass, which are the name and reference url. The updateUsingPassInfo takes in a dictionary and uses the nil coalescing operator when setting property values.

Next create a function to handle the job of consuming HTML data and producing model objects.

public func createPassInfo(from data: Data) -> PassInfo {
    let queries: [String: String] = [
        "conditions": "//span[@id='PassInfoConditions']",
        "eastbound": "//span[@id='PassInfoRestrictionsOne']",
        "westbound": "//span[@id='PassInfoRestrictionsTwo']",
        "last_updated": "//span[@id='PassInfoLastUpdate']"
    ]
    
    let doc = HTMLDoc(data: data)
    guard let root = doc.root else {
        return [:]
    }
    
    var info: [String: String] = [:]
    for (key, xpath) in queries {
        guard let value = root.evaluate(xpath: xpath).first?.nodeValue else { continue }
        info[key] = value
    }
    
    return info
}

There is one public class func createPassInfo that accepts Data and returns a PassInfo object. The PassInfo object is built by first loading the Data into an HTMLDoc and then enumerating over a set of XPaths and querying the document for information.

Resources

There’s a great article titled Wrapping libxml2 for Swift by Janie Clayton that goes into much more detail about using the library. Be sure the see the GitHub example too.

XML and Objective-C have been down this road before, and there’s plenty of applicable discussions about using the two together. See the two part article Taming HTML Parsing with libxml at Cocoanetics. For more on Xpaths, see Using libxml2 for XML parsing and XPath queries in Cocoa by Matt Gallagher.

Finally, there are many community contributed XML libraries written in Swift, such as Fuzi, Ji, or Kanna, which can be used as alternatives.

Networking With Futures

Up until this point, we’ve been able to get by with our dummy model just fine, but the app isn’t displaying real data so it’s not very useful. Next we’ll look at making network requests for remote data by creating a network controller. This will give us some real data to work with.

The network controller will be very similar to the one created in Luna, with one exception. We’ll be building on the concept of a Result type and defining a new type called Future. This type will represent some computation that has yet to occur.

Result Type

Create a generic Result type to represent some type T or an Error.

public enum Result<T> {
    case success(T)
    case failure(Error)

As before, we’ll use the Result type to represent the result of some operation or an error. Result will serve as a building block for us to define our own types on top of.

Network Controller

Next create a NetworkController struct that contains a private URLSessionConfiguration and URLSession.

public struct NetworkController: Reachable {
    
    fileprivate let configuration: URLSessionConfiguration
    fileprivate let session: URLSession
    
    init(with configuration: URLSessionConfiguration = URLSessionConfiguration.default) {
        self.configuration = configuration
        
        let queue = OperationQueue.main
        self.session = URLSession(configuration: configuration, delegate: nil, delegateQueue: queue)
    }
}

The configuration parameter in the initializer has a default value of URLSessionConfiguration.default. Allowing the configuration to be passed into the network controller will make it easier to test later on.

Next declare some typealiases for network tasks and an Error for any task errors that can occur.

public enum TaskError: Error {
    case offline
    case noData
    case badResponse
    case badStatusCode(Int)
    case other(Error)
}

public typealias TaskResult = Result<Data>
public typealias TaskCompletion = (Data?, URLResponse?, Error?) -> Void

public struct NetworkController {

    ...
}

The TaskResult type will be the result of some task, containing either Data or an error. The TaskCompletion type matches the signature of the completion handler used by URLSession. The signature is rather long so using a typealias keeps things a little cleaner.

Next we’ll create a dataForRequest: function on the network controller which takes in an URLRequest and returns a TaskFuture. We cannot fully implement this function yet because we haven’t discussed how to create and use futures, but here is the function signature we’re striving for.

func dataForRequest(request: NSURLRequest) -> Future<NSData>

With some groundwork laid for making network requests, let’s take a closer look at futures and see how to use them. Afterwards we will finish implementing data(for:request).

Introducing Futures

Futures are proxy objects used to represent the result of some computation that has yet to occur. Initially a future will not have a value, but will be assigned one later on, when the computation completes. The benefit is that the caller can work with the result at the call site, as if the computation has already occured, which allows asynchronous code to be a little cleaner.

Futures (sometimes called promises) can be found in other languages, such as Java, JavaScript, and C++. Several Objective-C libraries also exist to provide similar functionality, such as PromiseKit or Bolts.

For a Swift solution, we can turn to the excellent talk given by Javier Soto and leverage some of the code provided. In the talk, Javier outlines a really slick solution in about twenty lines of code. If you have time watch the entire talk; the Future implementation below is the same as the one discussed.

Create a new Swift file named Future and add the following type. While concise, there are a few important things going on here, so take the time the read through the definition.

public struct Future<T> {
    public typealias ResultType = Result<T>
    public typealias Completion = (ResultType) -> ()
    public typealias AsyncOperation = (@escaping Completion) -> ()
    
    private let operation: AsyncOperation
    
    public init(value result: ResultType) {
        self.init(operation: { completion in
            completion(result)
        })
    }
    
    public init(operation: @escaping AsyncOperation) {
        self.operation = operation
    }
    
    public func start(_ completion: @escaping Completion) {
        self.operation() { result in
            completion(result)
        }
    }
}

The Future type is generic and has some type T associated with it. Three typealiases are defined: one for a type and two for functions. The ResultType typealias represents a generic Result. We’ve seen this before already, but using a typealias provides a cleaner name than Result<T>. The Completion typealias is a function that accepts a ResultType and returns nothing. Similarly, the AsyncOperation typealias represents a function that accepts a Completion type (itself a function) and a returns nothing.

The Future type also defines two initializers. One initializer accepts a ResultType as a parameter, and the other accepts an AsyncOperation. There is a private AsyncOperation property that is set using the initializers. A start function is also defined, which takes in a Completion parameter, runs the AsyncOperation and calls the completion block when done.

Network Requests with Futures

Returning to the network controller, the next step is to define the TaskFuture type and finish implementing data(for:request).

public typealias TaskFuture = Future<NSData>

public class NetworkController {
    
    ...

    public func data(for request: URLRequest) -> TaskFuture {
        
        let future: TaskFuture = Future() { completion in
            
            let fulfill: (_ result: TaskResult) -> Void = {(taskResult) in
                switch taskResult {
                case .success(let data):
                    completion(.success(data))
                case .failure(let error):
                    completion(.failure(error))
                }
            }
            
            let completion: TaskCompletion = { (data, response, err) in
                guard let data = data else {
                    guard let err = err else {
                        return fulfill(.failure(TaskError.noData))
                    }
                    
                    return fulfill(.failure(TaskError.other(err)))
                }
                
                guard let response = response as? HTTPURLResponse else {
                    return fulfill(.failure(TaskError.badResponse))
                }
                
                switch response.statusCode {
                case 200...204:
                    fulfill(.success(data))
                default:
                    fulfill(.failure(TaskError.badStatusCode(response.statusCode)))
                }
            }
            
            let task = self.session.dataTask(with: request, completionHandler: completion)
            
            switch self.reachable {
            case .online:
                task.resume()
            case .offline:
                fulfill(.failure(TaskError.offline))
            }
        }
        
        return future
    }
}

There is a bit going on inside data(for:request) so take a moment to step through it. Notice that the function basically just declares a TaskFuture and returns it. Most of the remaining logic is inside the completion block of the TaskFuture. The fulfill function is used to fulfill a future and return the result to the caller. The completion function handles a response and calls fulfill with the appropriate result. The completion is passed along as a completion handler to the dataTaskWithRequest:completionHandler: method.

While the code above is using futures, the implementation is still very similar to the one discussed in Luna.

App Transport Security

In iOS 9 and OS X 10.11, Apple introduced a new security feature called App Transport Security that improves privacy and data protection by requiring applications to use secure https connections by default. If your app needs to communicate with a resource served via http, then you need to provide a security exception in your Info.plist.

Because the WSDOT driving information is only available via http, we must add an exception domain. While it is possible to add a single NSAllowsArbitraryLoads entry into your Info.plist and allow loading data from all http domains, specifying the exact resource we need is more secure.

<key>NSAppTransportSecurity</key>
<dict>
	<key>NSExceptionDomains</key>
	<dict>
		<key>www.wsdot.com</key>
		<dict>
			<key>NSExceptionAllowsInsecureHTTPLoads</key>
			<true/>
			<key>NSIncludesSubdomains</key>
			<true/>
		</dict>
	</dict>
</dict>

If your app attempts to load data from an http connection, and a matching exception domain hasn’t been specified, Xcode will report an error similar to the following.

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app’s Info.plist file.

Be sure to specify an exception domain if your app needs to access http resources.

Create a Signaler

To begin to use the TaskFuture type, we’ll create a signaler object to abstract away the NetworkController and provide a new future type for the Pass.

typealias PassFuture = Future<PassInfo>
typealias PassesFuture = Future<[PassInfo]>

final class PassSignaler {
    
    private let controller = NetworkController()
    private var error: Error? = nil
    
    func future(for passInfo: [PassInfo]) -> PassesFuture {
        // TODO: implement
    }
    
    private func future(for passInfo: PassInfo) -> PassFuture {
        // TODO: implement
    }
}

Aside from the empty functions, the implementation so far is fairly straightforward: there are two typealias declared that are futures for PassInfo types and there is a private NetworkController that will be used to make network requests.

Before moving on, let’s take a moment and look at why two functions are used for returning Pass future types. Remember that we’re working with a set a passes, and we want to update the data for each pass independently. This is because each pass has its own unique URL, so the data is coming from different end points. The private function future(for:passInfo takes in PassInfo for one pass and returns a Future for when data on that one pass is available. The public function future(for:passInfos) takes in an array for [PassInfo] and returns a Future for when updated data on all passes is available.

Let’s move on to implementing the missing functions and providing a more concrete example of using the PassFuture type.

First declare a PassError type to describe errors that could be encountered when handling pass data.

enum PassError: Error {
    case noData
    case offline
}

You can also make the PassError type conform to CustomStringConvertible in order to reutrn an NSLocalizedString for an error message.

extension PassError: CustomStringConvertible {
    var description: String {
        switch self {
        case .offline:
            return NSLocalizedString("Connection is Offline", comment: "Connection is Offline")
        case .noData:
            return NSLocalizedString("No Data Received", comment: "No Data Received")
        }
    }
}

Next return to the PassSignaler and implement the future(for:passInfo) function.

class PassSignaler {

    ...

    private func future(for passInfo: PassInfo) -> PassFuture {
        let future: PassFuture = Future() { completion in
            
            guard let urlString = passInfo[PassInfoKeys.ReferenceURL] else {
                return completion(Result.failure(PassError.noData))
            }
            
            guard let url = URL(string: urlString) else {
                return completion(Result.failure(PassError.noData))
            }
            
            let request = URLRequest(url: url)
            let future = self.controller.data(for: request)
            
            future.start { (result) -> () in
                
                let passResult = result.map({ (data) -> PassInfo in
                    var passInfo = Parser.createPassInfo(from: data)
                    for (key, value) in passInfo {
                        passInfo[key] = value
                    }
                    return passInfo
                })
                
                completion(passResult)
            }
        }
        
        return future
    }

    
}

At a high level, all the future(for:passInfo) function really does is declare a PassFuture and return it. Digging a little deeper, the PassFuture is initialized with a completion block that uses the NetworkController to get some information about a pass. The PassInfo dictionary comes into play because it provides the url to obtain pass data from. There is a private helper function createPassInfo(from:data) that is used to transform Data into PassInfo objects by using the HTML parsing methods discussed in the last chapter.

Next, implement the future(for:passInfo) function which takes an array of [PassInfo] objects and returns an array of [PassFuture] objects.

class PassSignaler {

    ...

    func future(for passInfo: [PassInfo]) -> PassesFuture {
        let future: PassesFuture = Future() { completion in
            
            let group = DispatchGroup()
            var updates: [PassInfo] = []
            
            for info in passInfo {
                group.enter()
                
                self.future(for: info).start({ (result) -> () in
                    switch result {
                    case .success(let info):
                        updates.append(info)
                    case .failure(let error):
                        self.error = error
                    }
                    
                    group.leave()
                })
            }
            
            let queue = DispatchQueue.global()
            
            group.notify(queue: queue, execute: { 
                if let error = self.error {
                    completion(Result.failure(error))
                }
                else if updates.count == 0 {
                    completion(Result.failure(PassError.noData))
                }
                else {
                    completion(Result.success(updates))
                }
            })
        }
        
        self.error = nil
        
        return future
    }
}

Notice that the futureForPassesInfo function uses a dispatch_group to determine when multiple HTTP requests have finished. This is the same approach used in Luna when waiting on the requests for Moon and Phase data.

Resources

For more information on futures with Swift be sure to check out Javier Soto’s talk titled Back to the Futures mentioned above. It’s a fantastic resource.

At the Functional Swift 2015 Conference in Brooklyn NY, Brian Partridge gave a talk on Results Driven Development that discusses the Result type in detail and how to leverage it during development.

Finally, for a bulletproof real-world implementation, the Result µframework by Antitypical is the place the look.

Building the UI

Up until now the user interface has been fairly simple, but now it’s time to build out a detail cell and start showing some useful information.

Start by creating a subclass of UICollectionViewCell named DetailCell or something similar. This will serve as the detail view for one pass. Check the box to create a XIB file along with the subclass.

The cell will contain several labels to display information about the pass. For example, the cell will need labels for the eastbound and westbound conditions and a label for the pass title and closure status. We also might want a label to show when the pass was last updated.

Before jumping in and positioning labels, let’s take a moment to consider how the interface should adjust for different text lengths. Because the pass closure information can be of varying length, the labels must be relatively positioned to each other, such that no overlap occurs when text expands. The item size dimensions are fixed, so in order to prevent text from flowing off the cell, a scroll view is used on each cell.

Scroll Views and Auto Layout

Using UIScrollView together with Auto Layout is a topic given special attention by Apple in Technical Note TN2154. Specifically mentioned is that “constraints on the subviews of the scroll view must result in a size to fill, which is [the] content size of the scroll view”. This means that, taken together, the constraints on the subviews need to define a rectangular boundary. This boundary then becomes the content size for the scroll view.

There are many different articles on using Auto Layout with UIScrollView, but a key approach many take is to create a top level container view as the first child of the scroll view. This container view has its constraints pinned on all four sides to the superview– the scroll view. Any subviews that need to be scrollable are added as children to the container view. The container view will grow as the subviews reposition and ultimately, the contentSize of the scroll view will be set based on the container view bounds.

As an example, consider the image below.

The container view with four labels represents the labels within the cell. These display pass information and reposition themselves relative to each, as the text length changes. To make the diagram simpler, only four labels are used, but the container view could have any number of subviews.

It’s important that the container view is placed inside a UIScrollView as the only child. Constraints on the container view are positioned such that all four sides are pinned to the superview (the scroll view). Adjacent to the scroll view is a UIView that acts as a status view, changing color to denote the pass closure status. This view does not scroll so it is placed as a sibling to the scroll view. Both exist as subviews within the larger UIView that belongs to the collection view cell.

Taking the above content view as a model for the cell, construct the necessary view hierarchy and wire up UILabel objects. In order for label text to properly wrap, the numberOfLines property should be set to zero.

The detail cell should resemble the following when complete.

Note how the view hierarchy on the left closely matches the diagram above; the difference being the cell has more than four labels.

Collection View Layout

In order to accommodate the larger cell, the detail collection view needs to have a different layout than the list view. Instead of scrolling vertically and filling the view with cells, the detail layout will scroll horizontally and display one cell. Adjacent cells will be available by swiping left and right.

As with the collection view cell, create another subclass of UICollectionViewFlowLayout to use with the detail cell. Override the init method and use it to configure properties, such as scrollDirection. To satisfy the protocol requirements we also need to implement initWithCoder.

final class DetailViewLayout: UICollectionViewFlowLayout {
    
    override init() {
        super.init()
        
        self.minimumInteritemSpacing = 0.0
        self.minimumLineSpacing = 30.0
        self.scrollDirection = .horizontal
        self.sectionInset = UIEdgeInsetsMake(8.0, 15.0, 0.0, 15.0)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

As before with ListCollectionViewLayout, define a helper function to calculate the item size for the layout based on the UIScreen bounds.

func detailLayoutItemSize(for bounds: CGRect) -> CGSize {
    let width = bounds.width - 30.0
    let height = bounds.height - 158.0
    
    return CGSize(width: width, height: height)
}

The 30 points removed from the width matches up with the 15 point inset on the left and right. The 158 points removed the the height is a constant that gives the desired card-like look.

Enable Paging

In order for the detail view cells to snap left and right when scrolling, and for the collection view to display a full cell, there are two things we need to take care of.

The first is to set the pagingEnabled property to true. This property comes from UIScrollView and allows the scroll view to stop at discrete intervals equal to the scroll view’s bounds. In practice, this means we can tell to collection to always show a centered cell when scrolling stops.

class DetailViewController: UICollectionViewController {

    override func awakeFromNib() {
        super.awakeFromNib()
        
        self.collectionView?.collectionViewLayout = DetailViewLayout()
        self.collectionView?.isPagingEnabled = true
    }
}

The second is to implement targetContentOffset(forProposedContentOffset:) on the collection view layout. This method will allow us to control where the collection view stops scrolling, and make sure a cell is always centered on the content bounds.

When targetContentOffset(forProposedContentOffset:) is called, the collection view is suggesting a CGPoint where scrolling should stop. We can either return this point as is or recalculate a new point based on some factors.

By looking at the proposedContentOffset, we can calculate the mid-point within the collection view bounds corresponding to the scrolling end location. We can then take this mid-point, compare it against the visible cells, and see which cell is closer. When the closest cell is found, the point can be adjusted by the difference. This way we can ensure scrolling only stops when centered above a cell.

This idea is nicely explained in both Centered Paging with Preview Cells on UICollectionView and Using Collection Views to Build the iPhone Multitasking Screen.

class DetailViewLayout: UICollectionViewFlowLayout {
    
    ...

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
        guard let collectionView = collectionView else { fatalError("missing collection view") }
        
        var offsetAdjustment = CGFloat(MAXFLOAT)
        let horizontalCenter = proposedContentOffset.x + (collectionView.bounds.width / 2.0)
        let targetRect = CGRect(x: proposedContentOffset.x, y: 0.0, width: collectionView.bounds.width, height: collectionView.bounds.height)
        let attributes = super.layoutAttributesForElements(in: targetRect) ?? []
        
        for attribute in attributes {
            let itemHorizontalCenter = attribute.center.x
            if (abs(itemHorizontalCenter - horizontalCenter) < abs(offsetAdjustment)) {
                offsetAdjustment = itemHorizontalCenter - horizontalCenter;
            }
        }
        
        return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
    }
}

According to the documentation, the CGPoint returned as a content offset should be based on the adjusted upper-left corner of the visible area. Takig this point, we can then compare it against the rectangles of the visible cells on sreen, and adjust it accordingly.

Adding Some Style

With a second cell and layout defined, the interface is progressing along nicely, but we’re still missing some style and theming. Next we’ll create a contained set of colors and fonts to use throughout the app by using nested structs and static constants.

To begin with, consider the following.

struct Styles {
    struct Color {
        static let Red = UIColor.red
        static let Orange = UIColor.orange
        static let Green = UIColor.green
    }
    
    struct Font {
        static let Helvetica = UIFont(name: "Helvetica", size: 14.0)
    }
}

With the nested structure defined above, we can refer to color and fonts using notation like Styles.Color.Red and Styles.Font.Helvetica.

This is great, but using the default UIColors isn’t going to cut it. We need to define our own color palette. To make things easier, we can create an extension on UIColor to create colors as we please.

extension UIColor {
    class func hexColor(string: String) -> UIColor {
        let set = NSCharacterSet.whitespacesAndNewlines
        var colorString = string.trimmingCharacters(in: set).uppercased()
        
        if (colorString.hasPrefix("#")) {
            let index = colorString.index(after: colorString.startIndex)
            colorString = colorString[index..<colorString.endIndex]
        }
        
        if (colorString.characters.count != 6) {
            return UIColor.gray
        }
        
        var rgbValue: UInt32 = 0
        Scanner(string: colorString).scanHexInt32(&rgbValue)
        
        return UIColor(
            red:   CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
            blue:  CGFloat(rgbValue & 0x0000FF) / 255.0,
            alpha: CGFloat(1.0)
        )
    }
}

The extension above lets us create UIColors using hex notation. There are a few more variations of the above shown in this gist.

Apart from defining colors and fonts, the big challenge is selecting which colors and fonts to use. Adobe Color CC and COLOURLovers both provide samples with user submitted color palettes. These make for a nice starting point to get inspired about colors. For selecting fonts, iOSFonts provides a list all fonts available by default, but you can also utilize custom fonts.

Animations

Next we’ll discuss how to create a custom animation between two view controllers, where both are nested in a navigation controller. This animation will be used to replace the traditional push animation, where one view controller slides to the left and the other presents itself from the right.

The implementation will come in two parts. First we’ll examine and implement the necessary protocols in order to create a straightforward fade animation. This will give us a starting point for understanding navigation animations and set us on a path towards creating a custom approach. Afterwards we’ll design an animation to segue between the list and detail views and build a custom transition between the view controllers.

Types of Animations

On iOS, animations typically use the UIKit or Core Animation framework. With UIKit, animations can be performed directly on UIView objects. Several properties on a view can be animated such as the frame, transform and alpha. Core Animation allows for more complex animations that operate on the CALayer backing a UIView.

Below we’ll look at one animation example for each framework. Each example will adjust the alpha property or opacity of a UIView over a period of two seconds.

First consider the UIKit example.

let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 360.0, height: 360.0))
let rectangle = UIView(frame: CGRect(x: 0, y: 0, width: 50.0, height: 50.0))

containerView.addSubview(rectangle)

rectangle.center = containerView.center
rectangle.backgroundColor = UIColor.blue

let duration = 2.0
let delay = 0.0
let options: UIViewAnimationOptions = [.repeat, .curveEaseInOut, .autoreverse]
let animations: () -> Void = {
    rectangle.alpha = 0.0
}

UIView.animate(withDuration: duration, delay: delay, options: options, animations: animations, completion: nil)

The first example shows a UIKit animation performed using the animateWithDuration function. This function accepts several parameters, including an animation callback function, which defines the actual animation. Inside the animations block, properties changed on the view will be animated over time. The different UIViewAnimationOptions supplied allow the animation to be configured. In this case the options specify that the animation should repeat, reverse automatically, and animate over a gentle Ease-In-Ease-Out curve.

Now, we’ll look at the Core Animation example.

let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 360.0, height: 360.0))
let rectangle = UIView(frame: CGRect(x: 0, y: 0, width: 50.0, height: 50.0))

containerView.addSubview(rectangle)

rectangle.center = containerView.center
rectangle.backgroundColor = UIColor.blue

let fade = CABasicAnimation(keyPath: "opacity")
fade.fromValue = 1.0
fade.toValue = 0.0
fade.duration = 2.0
fade.autoreverses = true
fade.repeatCount = Float.infinity

rectangle.layer.add(fade, forKey: "fade")

Here Core Animation is used to create and apply animations directly to a CALayer. A CABasicAnimation animation is created for the opacity key path, which is defined on the CALayer. The animation is then configured with a fromValue and toValue. These values relate back to the key path, and in this case specify the opacity should animate from 1.0 to 0.0. The animation is also configured to repeat, reverse automatically, and occur over a period of two seconds.

When run, both animations will show a blue square that gently fades in and out of view.

Adopting Animation Protocols

Custom view controller transitions were introduced in iOS 7 with the UIViewControllerAnimatedTransitioning protocol. Conforming objects are given three methods to implement in order to handle animation from one view controller to another. We’ll dive more into implementing these methods shortly.

Container view controllers, such as UINavigationController and UITabViewController, provide a chance for delegates to specify animator objects when transitioning between view controllers. This is done using the and UINavigationControllerDelegate or UITabBarControllerDelegate. Our app is centered around a navigation controller, so we’ll focus only on UINavigationControllerDelegate.

The UINavigationControllerDelegate protocol includes an method that conforming objects can use to return animator objects. The method navigationController:animationControllerForOperation can be used to return a custom animator object that implements the UIViewControllerAnimatedTransitioning protocol.

In the context of our app, let’s take a rough look at what we need to accomplish for custom view transitions to work.

  1. Hook into the navigation controller and conform to the UINavigationControllerDelegate protocol by implementing the animation delegate method
  2. Create an object that conforms to UIViewControllerAnimatedTransitioning and return this object from the UINavigationControllerDelegate method.
  3. Build the custom animation.

Step 3 is intentionally vague; let’s focus on steps 1 and 2 for now.

In order to conform to UINavigationControllerDelegate, we need to access the navigation controller and set some object as its delegate. Start by opening the AppDelegate and digging into the interface hierarchy from application:didFinishLaunchingWithOptions:. By the time this method is called, the interface has been loaded from storyboard and the root view controller is available.

class AppDelegate: UIResponder, UIApplicationDelegate {

    ...

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        guard let navController = self.window?.rootViewController as? UINavigationController else { return false }
        navController.delegate = self
        
        ...
        
        return true
    }
}

After using a guard statement to optionally cast the rootViewController to a UINavigationController, the controller’s delegate is set to the AppDelegate. Next change the AppDelegate to conform to UINavigationControllerDelegate via an extension.

extension AppDelegate: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // TODO: return custom animator objects
        
        return nil
    }
}

Because navigationController:animationControllerForOperation can return an Optional, we can return nil so that a standard animation is performed. Later on we’ll look at the toVC and fromVC properties and return a custom animator object.

Now for step 2. Create an NSObject subclass to handle animations. In the end, we’ll actually be using two animators, one for showing the detail view, and another for hiding the detail view. But to start with, we can create a basic object to perform both transitions.

Create a subclass of NSObject that conforms UIViewControllerAnimatedTransitioning. We’ll be refactoring some of this later on to change the name of the object.

class CustomAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    
    private let duration = 0.75
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.75
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        // TODO: implement
    }
}

Notice the TODO left empty; this is step 3 from above. Lets look closer at animateTransition: and the UIViewControllerContextTransitioning parameter. According to the documentation, the UIViewControllerContextTransitioning protocol should not be conformed to use custom code. Instead, UIKit will provide a conforming object to your delegate.

Using the UIViewControllerContextTransitioning context object, you animation logic should access to source and destination view controllers using viewControllerForKey:. A container view to use during the animation is provided by the context via the containerView function. The documentation also notes that completeTransition: should be called on the context when all animations have completed.

Next we’ll create a basic hide-and-show animation, where the source view controller fades out while the destination view controller fades in. This will illustrate how the use the UIViewControllerContextTransitioning context from within the animateTransition: function.

class CustomAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    
    ...
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? UICollectionViewController else { return }
        guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? UICollectionViewController else { return }
        
        toViewController.view.alpha = 0.0
        containerView.addSubview(toViewController.view)
        
        let animations: () -> Void = {
            toViewController.view.alpha = 1.0
            fromViewController.view.alpha = 0.0
        }
        
        let completion: Bool -> Void = { finished in
            fromViewController.view.removeFromSuperview()
            transitionContext.completeTransition(finished)
        }
        
        UIView.animate(withDuration:1.0, animations: animations, completion: completion)
    }
}

Lets walk through the animateTransition function. First the containerView is obtained from the transition context. The container view can be thought of as a blank UIView canvas where your animation will be performed. Next references to the view controllers in play are obtained from the context using the UITransitionContextFromViewControllerKey and UITransitionContextToViewControllerKey keys.

The container view already has the fromViewController view in its subview hierarchy. Our job is the add the toViewControllers view onto the containerView and to animate and rearrange the views as needed. In the example above, the alpha property on the toViewControllers view is set to 0.0 so the view is initially hidden. Then through an animation block, the alpha is set to 1.0 while simultaneously, the alpha on the fromViewController is set to 0.0. An animation completion block is used to remove the fromViewController from the subview hierarchy and to call completeTransition on the transition context.

Before the animator can be used, an instance of it must be returned from the appropriate navigation controller delegate method.

extension AppDelegate: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        return CustomAnimator()
    }
}

The custom animation should now appear.

Designing

Now lets look at designing the custom animation. Fading between two view controllers works well enough, but won’t win any style points.

When the start and end states are considered together, the transition can be viewed as the change in cell size between the two collection view layouts. For any cell that is tapped upon in the first view controller, the second view controller should present that cell in its layout. What we really want is an animation from the cell state one view controller to the corresponding cell state in a second view controller.

Eventually the animations will need to work forwards and backwards. The CustomAnimator doesn’t care which view controller is the source or destination, but for the animations we’ll be building, the view controllers will matter. For now, we’ll only address the list-to-detail transition.

As one possible animation, consider the following. When a cell is tapped upon, the other cells fade out, while the tapped cell resizes to match the new layout.

The animation above is exaggerated in order to show the three primary phases or steps.

  1. Find the selected collection view cell, and fade out all the other cells.
  2. Move the selected cell to the Y position of the corresponding cell in the new layout.
  3. Resize the selected cell to match the height of the layout.

There are two key concepts that must be addressed before building the animation.

One is the idea of creating a snapshot or picture of a view. There are several techniques that can be used to accomplish this but the idea is essentially the same: to have piece of the interface that can be positioned and moved around on the container view.

The second concept is key frame animations using animateKeyframesWithDuration method on UIView. With key frame animations, several individual animations can be lumped together, with each have its own relative start time and duration. As a whole, the group of animations is given a duration, and each individual key frame animation begin at its relative start time.

Building the animation above will leverage both of these concepts. A snapshot of the selected collection view cell will be created and placed on the container view. Key frame animations will be used to created the three distinct animations: fade, move, and expand.

Building an Animated Transition

With an idea of what the animation will look like, lets move onto building it.

In order to accomplish to cell movement and resizing, we’ll be using the snapshot API from UIView and creating and interim view that represents the resizing cell during the animation. This will be accomplish using the resizableSnapshotViewFromRect method. By using a snapshot, a light-weight view is created which can then be added to the container view and positioned accordingly.

Let’s discuss some of the setup and get a handle on whats needed before the animation begins. For one, we need to know what cell was selected or tapped upon from the collection view controller. This can be determined by finding the source collection view and using indexPathsForSelectedItems. We also need to know the itemSize in the layout for the destination collection view.

With a rough idea of what to do, next create ShowDetailAnimator object to show the detail view controller.

final class ShowDetailAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    private let duration = 0.75
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        // TODO: snapshot selected cell, add to container view
        
        let animations: () -> Void = {
            UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.33, animations: { 
                // TODO: fade source view controller to zero
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0.23, relativeDuration: 0.73, animations: {
                // TODO: move snapshot to new Y position
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0.36, relativeDuration: 0.64, animations: {
                // TODO: expand snapshot to match cell height
            })
        }
        
        let completion: (Bool) -> Void = { finished in
            transitionContext.completeTransition(finished)
            
            // TODO: cleanup by removing snapshot
        }
        
        UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: [], animations: animations, completion: completion)
    }
}

Essentially there are three things to do: snapshot the selected cell, animate the views, and cleanup. Some additional calculations will be needed in order to fully animate the views, such as determining starting and ending frames within the container view.

First create the snapshot using the resizableSnapshotViewFromRect function.

final class ShowDetailAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    
    ...

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? UICollectionViewController else { return }
        guard let fromCollectionView = fromViewController.collectionView else { return }
        let containerView = transitionContext.containerView

        guard let indexPath = fromCollectionView.indexPathsForSelectedItems?.first else { return }
        let attributes = fromCollectionView.layoutAttributesForItem(at: indexPath)
        guard let originRect = attributes?.frame else { return }
        
        guard let snapshot = fromCollectionView.resizableSnapshotView(from: originRect, afterScreenUpdates: false, withCapInsets: insets) else { return }
        snapshot.frame = containerView.convert(originRect, from: fromCollectionView)
        containerView.addSubview(snapshot)
        
        ...
    }
}

First the source collection view is found and from that the NSIndexPath of the selected cell. The collection view is then queried for the layout attributes associated with the index path. From the layout attributes, the origin rectangle is found matching the on screen coordinates of the cell and a snapshot is created. Finally the snapshot frame is adjusted the coordinate space of the container view before being added as a subview.

Next turn to the animation blocks and begin to fill each key frame in. The first key frame is straight forward, because we already have access to the source view controller. This is the fromViewController we obtain earlier. When is comes to moving the snapshot, we need to calculate updated frames for both positions.

let itemSize = DetailViewLayout.detailLayoutItemSize(for: UIScreen.main.bounds)
        
guard let originRect = attributes?.frame else { return }
let destinationRect = CGRect(x: 15.0, y: 115.0, width: itemSize.width, height: itemSize.height)
        
let firstRect = CGRect(x: destinationRect.origin.x, y: destinationRect.origin.y, width: destinationRect.size.width, height: originRect.size.height)
let secondRect = CGRect(x: destinationRect.origin.x, y: destinationRect.origin.y, width: destinationRect.size.width, height: destinationRect.size.height)

Building on what we calculated earlier, a destinationRect is found using the itemSize for the layout. From that, we can calculate two interstitial rectangles that the snapshot will be positioned. This gives up the frames needs for the last two animations.

There is some cleanup necessary when the animation finishes to the interface is left in the proper state. The source view controller needs to be removed from the stack while the destination view controller is added. The snapshot also needs to be removed the from view hierarchy. Finally, completeTransition must be called on the transition context.

The complete implementation is show below.

class ShowDetailAnimator: NSObject, UIViewControllerAnimatedTransitioning {

    ...

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? UICollectionViewController else { return }
        guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? UICollectionViewController else { return }
        
        guard let fromCollectionView = fromViewController.collectionView else { return }
        let containerView = transitionContext.containerView
        containerView.backgroundColor = AppStyle.Color.LightBlue
        
        guard let indexPath = fromCollectionView.indexPathsForSelectedItems?.first else { return }
        let attributes = fromCollectionView.layoutAttributesForItem(at: indexPath)
        let itemSize = DetailViewLayout.detailLayoutItemSize(for: UIScreen.main.bounds)
        
        guard let originRect = attributes?.frame else { return }
        let destinationRect = CGRect(x: 15.0, y: 115.0, width: itemSize.width, height: itemSize.height)
        
        let firstRect = CGRect(x: destinationRect.origin.x, y: destinationRect.origin.y, width: destinationRect.size.width, height: originRect.size.height)
        let secondRect = CGRect(x: destinationRect.origin.x, y: destinationRect.origin.y, width: destinationRect.size.width, height: destinationRect.size.height)
        let insets = UIEdgeInsets(top: 73.0, left: 0.0, bottom: 1.0, right: 0.0)
        
        guard let snapshot = fromCollectionView.resizableSnapshotView(from: originRect, afterScreenUpdates: false, withCapInsets: insets) else { return }
        snapshot.frame = containerView.convert(originRect, from: fromCollectionView)
        containerView.addSubview(snapshot)
        
        let animations: () -> Void = {
            UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.33, animations: { 
                fromViewController.view.alpha = 0.0
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0.23, relativeDuration: 0.73, animations: {
                snapshot.frame = firstRect
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0.36, relativeDuration: 0.64, animations: {
                snapshot.frame = secondRect
            })
        }
        
        let completion: (Bool) -> Void = { finished in
            transitionContext.completeTransition(finished)
            
            toViewController.view.alpha = 1.0
            fromViewController.view.removeFromSuperview()
            containerView.addSubview(toViewController.view)
            snapshot.removeFromSuperview()
        }
        
        UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: [], animations: animations, completion: completion)
    }
}

Resources

Animations Explained from objc.io is a great article with several examples for animations. The article is rich in detail and works up from basic to complex animations. There’s another one on View Controller Transitions that is equally as good.

The documentation from Apple on Animating Views is great for a deep dive on the different concepts at works when building animations.

Handling State Changes

Propogating state changes to the different actors is a big concern for all apps. In simple terms, consider the following questions. How will one view controller know when the data source changes? How will the data source know when to request updated information? Answering these type of questions properly is essential to creating a bug free app that responds as expected.

In our case, there are two key points to address when figuring out how data will flow through the app.

  1. How will the PassDataSource know when its time reload remote data?
  2. How will the PassViewController be made aware of changes to the PassDataSource?

Refresh Controller

To make things easier, a RefreshController can be created to abstract away some of the mechanics for reloading data. This will provide a cleaner separation of concerns and prevent the PassViewController from worrying about how data is loaded.

A UIRefreshControl is lazily created and added to the collection view inside PassViewController. This control provides the pull-to-refresh behavior and will trigger the data source to reload remote data.

class PassViewController: UICollectionViewController {

	...

    private lazy var refreshControl: UIRefreshControl = {
        let control = UIRefreshControl()
        
        control.addTarget(self, action: #selector(RefreshController.handleRefresh(_:)), for: .valueChanged)
        control.backgroundColor = UIColor.clear
        
        return control
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        ...

        self.collectionView?.addSubview(refreshControl)
    }

	func handleRefresh(_ sender: AnyObject) {
        // TODO: implement
    }
}

The RefreshController will maintain references to the PassDataSource and UIRefreshControl, and it will manage transitions between different states.

enum RefreshState {
    case idle
    case updating
    case error
}

final class RefreshController: NSObject {
    weak var refreshControl: UIRefreshControl?
    weak var dataSource: PassDataSource?
    
    init(refreshControl: UIRefreshControl, dataSource: PassDataSource) {
        self.refreshControl = refreshControl
        self.dataSource = dataSource
        
        super.init()
    }
}

The RefreshState enum defines the three states the controller can be in. Either the data is current and the controller is idle, the controller is in the process of updating the data, or the controller encountered an error trying to refresh the data. The RefreshController uses weak properties because it doesn’t own either the UIRefreshControl or the PassDataSource. Both of these objects are owned by the enclosing view controller.

Most of the logic in the refresh controller centers around updating the interface to reflect the state of the data source. Below, the label on the UIRefreshControl is set based on the current state and the setControlState function is used to move between states.

class RefreshController: NSObject {

    ...

    func setControlState(state: RefreshState) {
        guard let refreshControl = refreshControl else {
            fatalError("refreshControl should not be nil")
        }
        
        let string = title(for: state)
        refreshControl.attributedTitle = NSAttributedString(string: string)
        
        transition(to: state)
    }
}

extension RefreshController {
    fileprivate func transition(to state: RefreshState) {
        if state == .updating {
            let delayTime = DispatchTime.now() + 1
            DispatchQueue.main.asyncAfter(deadline: delayTime, execute: { [weak self] in
                self?.dataSource?.reloadData()
                })
            
        }
        else {
            self.refreshControl?.endRefreshing()
        }
    }
    
    fileprivate func title(for state: RefreshState) -> String {
        guard let dataSource = dataSource else {
            fatalError("dataSource should not be nil")
        }
        
        switch state {
        case .error:
            return NSLocalizedString("Error", comment: "Error")
        case .updating:
            return NSLocalizedString("Updating...", comment: "Updating")
        case .idle:
            let dateString = self.dateFormatter.string(from: dataSource.lastUpdated)
            let prefix = NSLocalizedString("Updated on", comment: "Updated on")
            return "\(prefix) \(dateString)"
        }
    }
}

In the example above, the setControlState: function is called from the view controller as the different states change. The title is then determined based on the state and an NSAttributedString is created for the refresh control. Finally, transitionToState: is called with the new state.

The state we’re most interested in is the .updating state, which triggers the data source to reload itself. If we’re not in an updating state, endRefreshing is called on the refresh control.

There are a few methods not shown. See the repository for the complete example.

Key Value Observing

The PassViewController still needs to be made aware of changes to the data source. For that we’ll use Key Value Observing. This requires the data source to be KVO compliant, which boils down to subclassing NSObject and using dynamic properties.

class PassDataSource: NSObject, NSCoding {
    
    dynamic private(set) var passes: [Pass] {
        didSet {
            self.lastUpdated = Date()
        }
    }
    
    dynamic private(set) var isUpdating: Bool = false
    dynamic private(set) var error: NSError? = nil

    ...
}

The PassViewController has an optional property for a PassDataSource. When this dataSource is set, the PassViewController is added as a key value observer for three properties on the data source.

let context = UnsafeMutablePointer<Void>(nil)

class PassViewController: UICollectionViewController {
    
    var dataSource: PassDataSource? {
        didSet {
            if let dataSource = dataSource {
                dataSource.addObserver(self, forKeyPath: "passes", options: [.new], context: context)
                dataSource.addObserver(self, forKeyPath: "updating", options: [.new], context: context)
                dataSource.addObserver(self, forKeyPath: "error", options: [.new], context: context)
            }
        }
    }

    ...

}

Once the PassViewController is observing the data source, the next step is to implement observeValueForKeyPath: and respond to key path changes. Using the context pointer to differentiate between function calls, check the keypath and handle each accordingly.

class PassViewController: UICollectionViewController {
	
	...

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == context {
            if keyPath == "passes" {
                handlePassesChange()
            } else if keyPath == "updating" {
                handleUpdatingChange()
            } else if keyPath == "error" {
                handleErrorChange()
            }
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}

The implementations for handlePassesChange(), handleUpdatingChange(), and handleErrorChange() aren’t shown, but it’s easy to see what needs to happen for each.

Finishing Touches

At some point, most software projects usually succumb to the ninety-ninety rule, where the last remaining pieces always seem to take the longest. In this regard, mobile apps are no different. There are always features that linger on and take longer than anticipated.

In this section we’ll look at wrapping up some loose ends and adding polish to the app.

Reachability

An app that communicates over the network should check if a connection is available and if the network is reachable. By doing so, an app can provide more context beyond a “Connection Failed” error and attempt to point to user towards fixing their network connection, be it Wi-Fi or Cellular.

Reachability is a popular sample code project from Apple that uses the System Configuration framework to determine network state. This idea has evolved into several open source libraries that also provide simular functionality, noteably Reachability project by Tony Million and the Swift Reachability counterpart by Ashley Mills (Yes, all the projects share the same original name).

Each offers examples of how to use the System Configuration framework and provides robust logic for determining the connection state. By scarficing some of this robustness, we can take the core idea and extract it into a slim Swift solution.

To start with, define a type and protocol that encapsulate the idea of having or not having a network connection.

public enum ReachabilityType {
    case online
    case offline
}

protocol Reachable {
    var reachable: ReachabilityType { get }
}

The ReachabilityType type simply says if we’re online or offine. We’re not interested in what type of connection we have, but only if we have one or not. This is modeled as an enum with two states. Second we define a Reachable protocol with a reachable variable that determines the state.

Next we can extend the Reachable protocol with a default implementation for the reachable variable. Using the ideas presented by the other sample projects, we can determine if a connection exists by attempting to get a set of SCNetworkReachabilityFlags and looking for the correct values.

import SystemConfiguration

extension Reachable {
    var reachable: ReachabilityType {
        var address = sockaddr_in()
        address.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        address.sin_family = sa_family_t(AF_INET)
        
        var reachability: ReachabilityType = ReachabilityType.offline
        
        let _ = withUnsafePointer(to: &address, { ptr in
            ptr.withMemoryRebound(to: sockaddr.self, capacity: 1, { (addr) -> Void in
                guard let reachable = SCNetworkReachabilityCreateWithAddress(nil, addr) else { return }
                
                var flags: SCNetworkReachabilityFlags = []
                if !SCNetworkReachabilityGetFlags(reachable, &flags) { return }
                
                reachability = ReachabilityType(reachabilityFlags: flags)
            })
        })
        
        return reachability
    }
}

First a socket address is constructed and passed into the SCNetworkReachabilityCreateWithAddress function, which tries to create a reachability reference. If successful, the resulting SCNetworkReachability pointer is used to get the SCNetworkReachabilityFlags. If either of these operations fail, the connection is considered offline.

Next the ReachabilityType is extended with an initializer that accepts some SCNetworkReachabilityFlags and sets the connection state accordingly.

extension ReachabilityType {
    public init(reachabilityFlags flags: SCNetworkReachabilityFlags) {
        let connectionRequired = flags.contains(.connectionRequired)
        let isReachable = flags.contains(.reachable)
        self = (!connectionRequired && isReachable) ? .online : .offline
    }
}

This creates a simple but powerful abstraction that we can use from any point within our app. For example, by adopting the Reachable protocol, the NetworkController has reachable property available to it. The reachable property can then be used by the network controller to determine to connection state before making requests.

public struct NetworkController: Reachable {

    ...
}

In practice, you’ll likely want to respond to connection changes as they happen. The current implemention only checks once to see if the connection is available. For a more robust solution, look at using SCNetworkReachabilitySetCallback to specifiy a callback function, similar to this implementation.

Perserving App State

State preservation and restoration provides a mechanism for iOS apps to maintain the state of view controllers and views between app launches. This allows apps to maintain a more consistent experience across launches by returning the user to a specific point in the app, right where they left off.

For example, consider an app where a cell has been selected and a detail view controller presented. Then imagine the app is background and (eventually) iOS terminates it. Without any state preservation, when relaunched the app would display the original view controller and not the selected detail view. If state preseravation and restoration is used, the app can build up the interface and show the detail view controller when launched.

There are a few changes to make in order to support state preservation and restoration.

First, it is an opt in feature to your app delegate must explictly support. You must implement two additional delegate methods and return true from both.

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
        return true
    }
    
    func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
        return true
    }
}

Secondly, all view controllers that will participate in state preservation will need restoriation identifiers. This includes container view controller such as UINavigationController and UITabBarController. A restoration identifier from the storyboard by selecting the view controller and opening identity inspector.

It might not make sense to preserve state for all view controllers. In this case, a view controller be opt out of state restoration by not providing a restoration identifier. Consider the edit view controller that is presented only while set some values are being modified. In such case, it may be better to not restore the view controller but instead return the user to the parent view controller.

Once the restoration identifier is set, the view controller needs to implement the encoding methods. These should feel very similar to the NSCoding methods implemented earlier.

class PassViewController: UICollectionViewController, SegueHandlerType {

	...

    override func encodeRestorableState(with coder: NSCoder) {
        coder.encode(self.dataSource, forKey: "dataSource")
        
        super.encodeRestorableState(with: coder)
    }
    
    override func decodeRestorableState(with coder: NSCoder) {
        guard let dataSource = coder.decodeObject(forKey: "dataSource") as? PassDataSource else { return }
        
        self.dataSource = dataSource
        
        super.decodeRestorableState(with: coder)
    }
}

For the PassViewController, all that’s required is to read and write the dataSource from the NSCoder object provided. In encodeRestorableState(with:coder), this simply means calling encodeObject:forKey: with the data source and key. With decodeRestorableState(with:coder), an extra guard statement is used to verify the decoded object matches the correct class before setting the dataSource.

Until Next Time

That about wraps things up for now. Ofcourse there’s much more that could be done with this app. In the current state, it’d be easy to add a Today Widget and Watch Extension to make pass information available from outside the app. Another good improvement might be to replace the NSCoding persistence with something a little more powerful, such as a Core Data or SQLite.

These are some great topics to discuss in the future.

Change Log

March 2016

  • Replaced stringly typed selector names with #selector expressions

September 2016

  • Refactored method and class names for Swift 3