Chapter 6. View Controllers

An iOS app’s interface is dynamic, and with good reason. On the desktop, an application’s windows can be big, and there can be more than one of them, so there’s room for lots of interface. With iOS, everything needs to fit on a single display consisting of a single window, which in the case of the iPhone can be almost forbiddingly tiny. The iOS solution is to introduce, at will, completely new interface — a new view, possibly with an elaborate hierarchy of subviews — replacing or covering the previous interface.

For this to work, regions of interface material — often the entire contents of the screen — must come and go in an agile fashion that is understandable to the user. There will typically be a logical, structural, and functional relationship between the view that was present and the view that replaces or covers it, and this relationship will need to be maintained behind the scenes, in your code, as well as being indicated to the user: multiple views may be pure alternatives or siblings of one another, or one view may be a temporary replacement for another, or views may be like successive pages of a book. Animation is often used to emphasize and clarify these relationships as one view is superseded by another. Navigational interface and a vivid, suggestive gestural vocabulary give the user an ability to control what’s seen and an understanding of the possible options: a tab bar whose buttons summon alternate views, a back button or a swipe gesture for returning to a previously visited view, a tap on an interface element to dive deeper into a conceptual world, a Done or Cancel button to escape from a settings screen, and so forth.

In iOS, the management of this dynamic interface is performed through view controllers. A view controller is an instance of UIViewController. Actually, a view controller is most likely to be an instance of a UIViewController subclass; the UIViewController class is designed to be subclassed, and you are very unlikely to use a plain vanilla UIViewController object without subclassing it. You might write your own UIViewController subclass; you might use a built-in UIViewController subclass such as UINavigationController or UITabBarController; or you might subclass a built-in UIViewController subclass such as UITableViewController (Chapter 8).

A view controller manages a single view (which can, of course, have subviews); its view property points to the view it manages. This is the view controller’s main view, or simply its view. A view controller’s main view has no explicit pointer to the view controller that manages it, but a view controller is a UIResponder and is in the responder chain just above its view, so it is the view’s nextResponder.

View Controller Responsibilities

A view controller’s most important responsibility is its view. A view controller must have a view (it is useless without one). If that view is to be useful, it must somehow get into the interface, and hence onto the screen; a view controller is usually responsible for seeing to that, too, but typically not the view controller whose view this is; rather, this will be taken care of by some view controller whose view is already in the interface. In many cases, this will happen automatically (I’ll talk more about that in the next section), but you can participate in the process, and for some view controllers you may have to do the work yourself. The reverse is also true: a view that comes may also eventually go, and the view controller responsible for putting a view into the interface will also be responsible for removing it.

A view controller will typically provide animation of the interface as a view comes or goes. Built-in view controller subclasses and built-in ways of summoning or removing a view controller and its view come with built-in animations. We are all familiar, for example, with tapping something to make new interface slide in from the side of the screen, and then later tapping a back button to make that interface slide back out again. In cases where you are responsible for getting a view controller’s view onto the screen, you are also responsible for providing the animation. And you can take complete charge of the animation even for built-in view controllers.

View controllers, working together, can save and restore state automatically. This feature helps you ensure that if your app is terminated in the background and subsequently relaunched, it will quickly resume displaying the same interface that was showing when the user last saw it.

The most powerful view controller is the root view controller. This is the view controller managing the root view, the view that sits at the top of the view hierarchy, as the one and only direct subview of the main window, acting as the superview for all other interface. I described in Chapter 1 how this view controller attains its lofty position: it is assigned to the window’s rootViewController property. The window then takes that view controller’s main view, gives it the correct frame (resizing it if necessary), and makes it its own subview. The root view controller bears ultimate responsibility for two important decisions about the behavior of your app:

Rotation of the interface
The user can rotate the device, and you might like the interface to rotate in response, to compensate. This decision is made, in large part, by the root view controller.
Manipulation of the status bar
The status bar is actually a secondary window belonging to the runtime, but the runtime consults the root view controller as to whether the status bar should be present and, if so, whether its text should be light or dark.

Above and beyond all this, view controllers are typically the heart of any app, by virtue of their role in the model–view–controller architecture: view controllers are controllers (hence the name). Views give the user something to tap, and display data for the user to see; they are view. The data itself is model. But the logic of determining, at any given moment, what views are shown, what data those views display, and what the response to the user’s gestures should be, is controller logic. Typically, that means view controller logic. In any app, view controllers will be the most important controllers — frequently, in fact, the only controllers. View controllers are where you’ll put the bulk of the code that actually makes your app do what your app does.

View Controller Hierarchy

As I said in the previous section, there is always one root view controller, along with its view, the root view. There may also be other view controllers, each of which has its own main view. Such view controllers are subordinate to the root view controller. In iOS, there are two subordination relationships between view controllers:

Parentage (containment)

A view controller can contain another view controller. The containing view controller is the parent of the contained view controller; the contained view controller is a child of the containing view controller. A containment relationship between two view controllers is reflected in their views: the child view controller’s view, if it is in the interface at all, is a subview (at some depth) of the parent view controller’s view.

The parent view controller is responsible for getting a child view controller’s view into the interface, by making it a subview of its own view, and (if necessary) for removing it later. Introduction of a view, removal of a view, and replacement of one view with another often involve a parent view controller managing its children and their views.

A familiar example is the navigation interface: the user taps something and new interface slides in from the side, replacing the current interface. Figure 6-1 shows the TidBITS News app displaying a typical iPhone interface, consisting of a list of story headlines and summaries. This interface is managed by a parent view controller (a UINavigationController) with a child view controller whose view is the list of headlines and summaries. If the user taps an entry in the list, the whole list will slide away to one side and the text of that story will slide in from the other side; the parent view controller has added a new child view controller, and has manipulated the views of its children to bring about this animated change of the interface. The parent view controller itself, meanwhile, stays put — and so does its own view. (In this example, the UINavigationController is the root view controller, and its view is the root view.)

The TidBITS News app
Figure 6-1. The TidBITS News app
Presentation (modal views)

A view controller can present another view controller. The first view controller is the presenting view controller (not the parent) of the second; the second view controller is the presented view controller (not a child) of the first. The second view controller’s view replaces or covers, completely or partially, the first view controller’s view.

The name of this mechanism, and of the relationship between the view controllers involved, has changed over time. In iOS 4 and before, the presented view controller was called a modal view controller, and its view was a modal view; there is an analogy here to the desktop, where a window is modal if it sits in front of, and denies the user access to, the rest of the interface until it is explicitly dismissed. The terms presented view controller and presented view are more recent and more general, but the historical term “modal” still appears in the documentation and in the API.

A presented view controller’s view does indeed sometimes look rather like a desktop modal view; for example, it might have a button such as Done or Save for dismissing the view, the implication being that this is a place where the user must make a decision and can do nothing else until the decision is made. However, as I’ll explain later, that isn’t the only use of a presented view controller.

There is thus a hierarchy of view controllers. In a properly constructed iOS app, there should be exactly one root view controller, and it is the only nonsubordinate view controller — it has neither a parent view controller nor a presenting view controller. Any other view controller, if its view appears in the interface, must be a child view controller of some parent view controller or a presented view controller of some presenting view controller.

Moreover, there is a clear relationship between the view hierarchy and the view controller hierarchy. Recall that, for a parent view controller and child view controller, the child’s view, if present in the interface, must be a subview of the parent’s view. Similarly, for a presenting view controller and presented view controller, the presented view controller’s view is either a subview of, or completely replaces, the presenting view controller’s view. In this way, the actual views of the interface form a hierarchy dictated by and parallel to some portion of the view controller hierarchy: every view visible in the interface owes its presence to a view controller’s view, either because it is a view controller’s view, or because it’s a subview of a view controller’s view.

The place of a view controller’s view in the view hierarchy will often be automatic. You might never need to put a UIViewController’s view into the view hierarchy manually. You’ll manipulate view controllers; their hierarchy and their built-in functionality will construct and manage the view hierarchy for you.

For example, in Figure 6-1, we see two interface elements:

  • The navigation bar, containing the TidBITS logo.
  • The list of stories, which is actually a UITableView.

I will describe how all of this comes to appear on the screen through the view controller hierarchy and the view hierarchy (Figure 6-2):

  • The app’s root view controller is a UINavigationController; the UINavigationController’s view is the window’s sole immediate subview (the root view). The navigation bar is a subview of that view.
  • The UINavigationController contains a second UIViewController — a parent–child relationship. The child is a custom UIViewController subclass; its view is what occupies the rest of the window, as another subview of the UINavigationController’s view. That view is the UITableView. This architecture means that when the user taps a story listing in the UITableView, the whole table will slide out, to be replaced by the view of a different UIViewController, while the navigation bar stays.
The TidBITS News app’s initial view controller and view hierarchy
Figure 6-2. The TidBITS News app’s initial view controller and view hierarchy

In Figure 6-2, notice the word “automatic” in the two large right-pointing arrows associating a view controller with its view. This is intended to tell you how the view controller’s view became part of the view hierarchy. The UINavigationController’s view became the window’s subview automatically, by virtue of the UINavigationController being the window’s rootViewController. The custom UIViewController’s view became the UINavigationController’s view’s subview automatically, by virtue of the UIViewController being the UINavigationController’s child.

Sometimes, you’ll write your own parent view controller class. In that case, you will be doing the kind of work that the UINavigationController was doing in that example, so you will need to put a child view controller’s view into the interface manually, as a subview (at some depth) of the parent view controller’s view.

I’ll illustrate with another app of mine (Figure 6-3). The interface displays a flashcard containing information about a Latin word, along with a toolbar (the dark area at the bottom) where the user can tap an icon to choose additional functionality.

A Latin flashcard app
Figure 6-3. A Latin flashcard app
The Latin flashcard app’s initial view controller and view hierarchy
Figure 6-4. The Latin flashcard app’s initial view controller and view hierarchy

Again, I will describe how the interface shown in Figure 6-3 comes to appear on the screen through the view controller hierarchy and the view hierarchy (Figure 6-4). The app actually contains over a thousand of these Latin words, and I want the user to be able to navigate between flashcards to see the next or previous word; there is an excellent built-in view controller for this purpose, the UIPageViewController. However, that’s just for the card; the toolbar at the bottom stays there, so the toolbar can’t be inside the UIPageViewController’s view. Therefore:

  • The app’s root view controller is my own UIViewController subclass, which I call RootViewController; its view contains the toolbar, and is also to contain the UIPageViewController’s view. My RootViewController’s view becomes the window’s subview (the root view) automatically, by virtue of the RootViewController’s being the window’s rootViewController.
  • In order for the UIPageViewController’s view to appear in the interface, since it is not the root view controller, it must be some view controller’s child. There is only one possible parent — my RootViewController. My RootViewController must function as a custom parent view controller, with the UIPageViewController as its child. So I have made that happen, and I have therefore also had to put the UIPageViewController’s view manually into my RootViewController’s view.
  • I hand the UIPageViewController an instance of my CardController class (another UIViewController subclass) as its child, and the UIPageViewController displays the CardController’s view automatically.

Finally, here’s an example of a presented view controller. My Latin flashcard app has a second mode, where the user is drilled on a subset of the cards in random order; the interface looks very much like the first mode’s interface (Figure 6-5), but it behaves completely differently.

The Latin flashcard app, in drill mode
Figure 6-5. The Latin flashcard app, in drill mode
The Latin flashcard app’s drill mode view controller and view hierarchy
Figure 6-6. The Latin flashcard app’s drill mode view controller and view hierarchy

To implement this, I have another UIViewController subclass, DrillViewController; it is structured very much like RootViewController. When the user is in drill mode, a DrillViewController is being presented by the RootViewController, meaning that the DrillViewController’s interface takes over the screen automatically: the DrillViewController’s view, with its whole subview hierarchy, including the views of the DrillViewController’s children in the view controller hierarchy, replaces the RootViewController’s view and its whole subview hierarchy (Figure 6-6). The RootViewController and its hierarchy of child view controllers remains in place, but the corresponding view hierarchy is not in the interface; it will be returned to the interface automatically when we leave drill mode (because the presented DrillViewController is dismissed), and the situation will look like Figure 6-4 once again.

For any app that you write, you should be able to construct a diagram showing the hierarchy of view controllers and charting how each view controller’s view fits into the view hierarchy. The diagram should be similar to mine! The view hierarchy should run neatly parallel with the view controller hierarchy; there should be no crossed wires or orphan views. And every view controller’s view should be placed automatically into the view hierarchy, unless you have written your own parent view controller.

Warning

Do not put a view controller’s view into the interface manually, unless one of the following is the case:

  • The view controller is the child of your custom parent view controller. There is a complicated parent–child dance you have to do. See Container View Controllers.
  • You’re doing a custom transition animation. See Custom Transition.

View Controller Creation

A view controller is an instance like any other instance, and it is created like any other instance — by instantiating its class. You might perform this instantiation in code; in that case, you will of course have to initialize the instance properly as you create it. Here’s an example from one of my own apps:

let llc = LessonListController(terms: self.data)
let nav = UINavigationController(rootViewController:llc)

In that example, LessonListController is my own UIViewController subclass, so I have called its designated initializer, which I myself have defined; UINavigationController is a built-in UIViewController subclass, and I have used one of its convenience initializers.

Alternatively, a view controller instance might come into existence through the loading of a nib. To make it possible to get a view controller into the nib in the first place, view controllers are included among the object types available through the Object library in the nib editor.

It is legal, though in practice not common, for a .xib file to contain a view controller. A .storyboard file, on the other hand, is chock full of view controllers; view controllers are the basis of a storyboard’s structure, with each scene consisting of and corresponding to one view controller object. A view controller in a storyboard will go into a nib file in the built app, and that nib file will be loaded when the view controller instance is needed. Usually, that happens automatically. Nevertheless, a view controller in a storyboard is an ordinary nib object and, if it is to be used in the running app, will be instantiated through the loading of the nib just like any other nib object. I’ll give full details on how and why a view controller is instantiated from a storyboard later in this chapter.

Once a view controller comes into existence, it must be retained so that it will persist. This will happen automatically when the view controller is assigned a place in the view controller hierarchy that I described in the previous section. A view controller assigned as a window’s rootViewController is retained by the window. A view controller assigned as another view controller’s child is retained by the parent view controller. A presented view controller is retained by the presenting view controller. The parent view controller or presenting view controller then takes ownership, and will release the other view controller in good order when it is no longer needed.

Here’s an example, from one of my apps, of view controllers being instantiated and then being retained by being placed into the view controller hierarchy:

let llc = LessonListController(terms: self.data) 1
let nav = UINavigationController(rootViewController:llc) 2
self.presentViewController(nav, animated:true, completion:nil) 3

That’s the same code I showed a moment ago, extended by one line. It comes from a view controller class called RootViewController. Here’s how view controller creation and memory management works in those three lines:

1

I instantiate LessonListController.

2

I instantiate UINavigationController, and I assign the LessonListController instance to the UINavigationController instance as its child; the navigation controller retains the LessonListController instance and takes ownership of it.

3

I present the UINavigationController instance on self, a RootViewController instance; the RootViewController instance is the presenting view controller, and it retains and takes ownership of the UINavigationController instance as its presented view controller. The RootViewController instance itself is already the window’s rootViewController, and is retained by the window — and so the view controller hierarchy is safely established.

All of this sounds straightforward, but it is worth dwelling on, because things can go wrong. It is quite possible, if things are mismanaged, for a view controller’s view to get into the interface while the view controller itself is allowed to go out of existence. This must not be permitted. If such a thing happens, at the very least the view will apparently misbehave, failing to perform its intended functionality, because that functionality is embodied by the view controller, which no longer exists. (I’ve made this mistake, so I speak from experience here.) If you instantiate a view controller in code, you should immediately ask yourself who will be retaining this view controller.

If a view controller is instantiated automatically from a storyboard, it will be retained automatically. That isn’t magic, however; it’s done in exactly the same ways I just listed — by assigning it as the window’s rootViewController, or by making it another view controller’s child view controller or presented view controller.

How a View Controller Gets Its View

Initially, when it first comes into existence, a view controller has no view. A view controller is a small, lightweight object; a view is a relatively heavyweight object, involving interface elements that occupy memory. Therefore, a view controller postpones obtaining its view until it has to do so, namely, when it is asked for the value of its view property. At that moment, if its view property is nil, the view controller sets about obtaining its view. (We say that a view controller loads its view lazily.) Typically, this happens because it is time to put the view controller’s view into the interface.

In working with a newly instantiated view controller, be careful not to refer to its view property if you don’t need to, since this will trigger the view controller’s obtaining its view prematurely. (As usual, I speak from experience here.) To learn whether a view controller has a view without causing it to load its view, call isViewLoaded. New in iOS 9, you can refer to a view controller’s view safely, without loading it, as its viewIfLoaded (an Optional); you can also cause the view controller to load its view explicitly, rather than as a side effect of mentioning its view, by calling loadViewIfNeeded.

As soon as a view controller has its view, its viewDidLoad method is called. If this view controller is an instance of your own UIViewController subclass, viewDidLoad is your opportunity to modify the contents of this view — to populate it with subviews, to tweak the subviews it already has, and so forth — as well as to perform other initializations of the view controller consonant with its acquisition of a view. The view property is now pointing to the view, so it is safe to refer to self.view. Bear in mind, however, that the view may not yet be part of the interface! In fact, it almost certainly is not. (To confirm this, check whether self.view.window is nil.) Thus, for example, you cannot necessarily rely on the dimensions of the view at this point to be the dimensions that the view will assume when it becomes visible in the interface. Performing certain customizations prematurely in viewDidLoad is a common beginner mistake. I’ll have more to say about that later in the chapter.

Before viewDidLoad will be called, however, the view controller must obtain its view. The question of where and how the view controller will get its view is often crucial. In some cases, to be sure, you won’t care about this; in particular, when a view controller is an instance of a built-in UIViewController subclass such as UINavigationController or UITabBarController, its view is out of your hands — you might never even have cause to refer to it over the entire course of your app’s lifetime — and you simply trust that the view controller will somehow generate its view. But when the view controller is an instance of your own subclass of UIViewController, and when you yourself will design or modify its view, it becomes essential to understand the process whereby a view controller gets its view.

This process is not difficult to understand, but it is rather elaborate, because there are multiple possibilities. Most important, this process is not magic. Yet it quite possibly causes more confusion to beginners than any other matter connected with iOS programming. Therefore, I will explain it in detail. The more you know about the details of how a view controller gets its view, the deeper and clearer will be your understanding of the entire workings of your app, its view controllers, its .storyboard and .xib files, and so on.

The alternatives are as follows:

  • The view may be created in the view controller’s own code, manually.
  • The view may be created as an empty generic view, automatically.
  • The view may be created in its own separate nib.
  • The view may be created in a nib, which is the same nib from which the view controller itself is instantiated.

Manual View

To supply a UIViewController’s view manually, in code, implement its loadView method. Your job here is to obtain an instance of UIView (or a subclass of UIView) and assign it to self.view. You must not call super (for reasons that I’ll make clear later on).

Warning

Do not confuse loadView with viewDidLoad. Yes, I’ve made this mistake myself. I confess! loadView creates the view; viewDidLoad is called afterward.

Let’s try it. We are going to do everything manually, so we don’t need or want a storyboard; therefore, start with an app without a main storyboard (I explained how to make such an app at the start of Chapter 1), and modify it as follows:

  1. We need a UIViewController subclass, so choose File → New → File; specify iOS → Source → Cocoa Touch Class. Click Next.
  2. Name the class RootViewController, and specify that it is to be a UIViewController subclass. Uncheck “Also create XIB file” (if it happens to be checked). Click Next.
  3. Confirm that we’re saving into the appropriate folder and group, and that these files will be part of the app target. Click Create.

We now have a RootViewController class, and we proceed to edit its code. In RootViewController.swift, we’ll implement loadView. To convince ourselves that the example is working correctly, we’ll give the view an identifiable color, and we’ll put some interface inside it, namely a “Hello, World” label:

override func loadView() {
    let v = UIView()
    v.backgroundColor = UIColor.greenColor()
    self.view = v
    let label = UILabel()
    v.addSubview(label)
    label.text = "Hello, World!"
    label.autoresizingMask = [
        .FlexibleTopMargin,
        .FlexibleLeftMargin,
        .FlexibleBottomMargin,
        .FlexibleRightMargin]
    label.sizeToFit()
    label.center = CGPointMake(v.bounds.midX, v.bounds.midY)
    label.frame.makeIntegralInPlace()
}

We have not yet given a RootViewController instance a place in our view controller hierarchy — in fact, we have no view controller hierarchy. Let’s make one. To do so, we turn to AppDelegate.swift. (It’s a little frustrating having to set things up in two different places before our labors can bear any visible fruit, but such is life.)

In AppDelegate.swift, modify the implementation of application:didFinishLaunchingWithOptions: to create a RootViewController instance and make it the window’s rootViewController (see Example 1-1):

import UIKit
@UIApplicationMain
class AppDelegate : UIResponder, UIApplicationDelegate {
    var window : UIWindow?
    func application(application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?)
        -> Bool {
            self.window = UIWindow()
            let theRVC = RootViewController() // *
            self.window!.rootViewController = theRVC // *
            self.window!.backgroundColor = UIColor.whiteColor()
            self.window!.makeKeyAndVisible()
            return true
    }
}

Build and run the app. Sure enough, there’s our green background and our “Hello, world” label!

When we created our view controller’s view (self.view), we never gave it a reasonable frame. This is because we are relying on someone else to frame the view appropriately. In this case, the “someone else” is the window, which responds to having its rootViewController property set to a view controller by framing the view controller’s view appropriately as the root view before putting it into the window as a subview. In general, it is the responsibility of whoever puts a view controller’s view into the interface to give the view the correct frame — and this will never be the view controller itself (although under some circumstances the view controller can express a preference in this regard). Indeed, the size of a view controller’s view may be changed as it is placed into the interface, and you must keep in mind, as you design your view controller’s view and its subviews, that this can happen. (That’s why, in the preceding code, I used autoresizing to keep the label centered in the view, no matter how the view may be resized.)

Generic Automatic View

We should distinguish between creating a view and populating it. The preceding example fails to draw this distinction. The lines that create our RootViewController’s view are merely these:

let v = UIView()
self.view = v

Everything else configures and populates the view, turning it green and putting a label into it. A more appropriate place to populate a view controller’s view is its viewDidLoad implementation, which, as I’ve already mentioned, is called after the view exists and can be referred to as self.view. We could therefore rewrite the preceding example like this (just for fun, I’ll use autolayout this time):

override func loadView() {
    let v = UIView()
    self.view = v
}
override func viewDidLoad() {
    super.viewDidLoad()
    let v = self.view
    v.backgroundColor = UIColor.greenColor()
    let label = UILabel()
    v.addSubview(label)
    label.text = "Hello, World!"
    label.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activateConstraints([
        label.centerXAnchor.constraintEqualToAnchor(v.centerXAnchor),
        label.centerYAnchor.constraintEqualToAnchor(v.centerYAnchor),
    ])
}

But if we’re going to do that, we can go even further and remove our implementation of loadView altogether! It turns out that if you don’t implement loadView, and if no view is supplied in any other way, then UIViewController’s default implementation of loadView will do exactly what we are already doing in code: it creates a generic UIView object and assigns it to self.view. If we needed our view controller’s view to be a particular UIView subclass, that wouldn’t be acceptable; but in this case, our view controller’s view is a generic UIView object, so it is acceptable. Comment out or delete the entire loadView implementation from the preceding code, and build and run the app; our example still works!

View in a Separate Nib

A view controller’s view can be supplied from a nib file. This approach gives you the convenience of configuring and populating the view by designing it graphically in the nib editor interface.

When the nib loads, the view controller instance will already have been created, and it will serve as the nib’s owner. The view controller’s proxy in the nib has the same class as the view controller, and its view outlet points to the view object in the nib. Thus, when the nib loads, the view controller obtains its view through the nib-loading mechanism!

I’ll illustrate by modifying the preceding example to use a .xib file. (I’ll deal later with the use of a .storyboard file; knowing first how the process works for a .xib file will greatly enhance your understanding of how it works for a .storyboard file.)

In a .xib file, the nib owner is represented by the File’s Owner proxy object. Therefore, in a .xib file that is to serve as the source of a view controller’s view, the following two things must be true:

  • The File’s Owner class must be set to a UIViewController subclass (depending on the class of the view controller whose view this will be).
  • The File’s Owner proxy now has a view outlet, corresponding to a UIViewController’s view property. This outlet must be connected to the view.

Let’s try it. We begin with the example we’ve already developed, with our RootViewController class. Delete the implementation of loadView and viewDidLoad from RootViewController.swift, because we want the view to come from a nib and we’re going to populate it in the nib. Then:

  1. Choose File → New → File and specify iOS → User Interface → View. This will be a .xib file containing a UIView object. Click Next.
  2. Name the file MyNib (meaning MyNib.xib). Confirm the appropriate folder and group, and make sure that the file will be part of the app target. Click Create.
  3. Edit MyNib.xib. Prepare it in the way I described a moment ago:

    1. Set the File’s Owner class to RootViewController (in the Identity inspector).
    2. Connect the File’s Owner view outlet to the View object.
  4. Design the view. To make it clear that this is not the same view we were creating previously, perhaps you should give the view a red background color (in the Attributes inspector). Drag a UILabel into the middle of the view and give it some text, such as “Hello, World!”

We have designed the nib, but we have done nothing as yet to associate this nib with our RootViewController instance. To do so, let’s once again return to AppDelegate.swift, where we create our RootViewController instance:

let theRVC = RootViewController()
self.window!.rootViewController = theRVC

We’re going to modify this code so that our RootViewController instance, theRVC, is aware of this nib file, MyNib.xib, as its own nib file. That way, when theRVC needs to acquire its view, it will load that nib file with itself as owner, thus ending up with the correct view as its own view property. A UIViewController has a nibName property for this purpose. However, we are not allowed to set its nibName property (it is read-only). Instead, as we instantiate the view controller, we use the designated initializer, init(nibName:bundle:), like this:

let theRVC = RootViewController(nibName:"MyNib", bundle:nil)
self.window!.rootViewController = theRVC

(The nil argument to the bundle: parameter specifies the main bundle, which is almost always what you want.)

To prove that this works, build and run. The red background appears! Our view is loading from the nib.

Now I’m going to describe a shortcut based on the name of the nib. It turns out that if the nib name passed to init(nibName:bundle:) is nil, a nib will be sought automatically with the same name as the view controller’s class. Moreover, UIViewController’s init() calls init(nibName:bundle:), passing nil for both arguments. This means, in effect, that we can return to using init() to initialize the view controller, provided that the nib file has a name that matches the name of the view controller class.

Let’s try it. Rename MyNib.xib to RootViewController.xib, and change the code that instantiates and initializes our RootViewController back to what it was before, like this:

let theRVC = RootViewController()
self.window!.rootViewController = theRVC

Build and run. It works!

There’s an additional aspect to this shortcut based on the name of the nib. It seems ridiculous that we should end up with a nib that has “Controller” in its name merely because our view controller, as is so often the case, has “Controller” in its name. A nib, after all, is not a controller. It turns out that the runtime, in looking for a view controller’s corresponding nib, will in fact try stripping “Controller” off the end of the view controller class’s name. Thus, we can name our nib file RootView.xib instead of RootViewController.xib, and it will still be properly associated with our RootViewController instance.

When you create a UIViewController subclass, the Xcode dialog has a checkbox (which we unchecked earlier) offering to create an eponymous .xib file at the same time. If you accept that option, the nib is created with the File’s Owner’s class already set to the view controller’s class and with its view outlet already hooked up to the view. This automatically created .xib file does not have “Controller” stripped off the end of its name; you can rename it manually later (I generally do) if the default name bothers you.

Another convention involving the nib name has to do with the rules for loading resources by name generally. The same naming rule that I mentioned in Chapter 2 for an image file extended by the suffix ~ipad applies to nib files. A nib file named RootViewController~ipad.xib will be loaded on an iPad when a nib named "RootViewController" is sought. This principle can greatly simplify your life when you’re writing a universal app (though conditional constraints, described in Chapter 1, may permit you to design an interface differing on iPad and iPhone in a single nib).

We are now in a position to summarize the sequence whereby a view controller’s view is obtained:

  1. When the view controller first decides that it needs its view, loadView is always called.
  2. If we override loadView, we supply and set the view in code, and we do not call super. Therefore the process of seeking a view comes to an end.
  3. If we don’t override loadView, UIViewController’s built-in default implementation of loadView is used. It is this default implementation of loadView that loads the view controller’s associated nib. That is why, if we do override loadView, we must not call super — that would cause us to get both behaviors!
  4. If the previous steps all fail — we don’t override loadView, and there is no associated nib — UIViewController’s default implementation of loadView creates a generic UIView.

Warning

It follows from what I’ve just said that if a view controller’s view is to come from a nib, you should not implement loadView. I’ve made this mistake. The results were not pretty.

Nib-Instantiated View Controller

As I mentioned earlier, a view controller can be a nib object, to be instantiated through the loading of the nib. In the nib editor, the Object library contains a View Controller (UIViewController) as well as several built-in UIViewController subclasses. Any of these can be dragged into the canvas. This is the standard way of creating a scene in a .storyboard file; doing the same with a .xib file is rare but perfectly possible.

When a view controller has been instantiated from a nib, and when it comes eventually to obtain its view, all the ways I’ve already described whereby a view controller can obtain its view still apply. (There’s also an additional way, which I’ll discuss in a moment.)

To try this with a .xib file, start over with an empty project without a storyboard:

  1. Choose File → New → File and specify iOS → User Interface → Empty. Click Next.
  2. Name the new file Main.xib, make sure it’s being saved into the right place and that it is part of the app target, and click Create.
  3. Edit Main.xib. Drag a plain vanilla View Controller object into the canvas.
  4. In the view controller you just dragged into the canvas, there’s a View object serving as the main view. Select it and delete it! Don’t worry, we’ll discuss in a moment what it’s for.

In AppDelegate.swift, we must now arrange to load Main.xib and extract the view controller instance created from the nib object we just put into the nib, making that view controller our app’s root view controller. Here’s one very simple way to do that:

let arr = UINib(nibName: "Main", bundle: nil)
    .instantiateWithOwner(nil, options: nil)
self.window!.rootViewController = arr[0] as? UIViewController

You can now proceed, if you like, to experiment with various ways of helping this view controller get its view. At the moment it is a plain UIViewController instance. Let’s make it a class of our own:

  1. Give the project a RootViewController class (a UIViewController subclass).
  2. In Main.xib, select the view controller object and use its Identity inspector to set its class to RootViewController.

Now you can help RootViewController get its view, in any of the ways we’ve already explored:

  • Implement loadView in RootViewController.
  • Or, implement viewDidLoad but not loadView in RootViewController.
  • Or, add another nib called RootViewController.xib (or RootView.xib).

If this nib-instantiated view controller is to get its view from another nib, there’s a way to specify the name of that nib in the nib editor: select the view controller in its nib, and enter the name of the view’s nib in the NIB Name field in its Attributes inspector. This is the equivalent of specifying a nib name when you call init(nibName:bundle:).

When a nib contains a view controller, there is, as I hinted a moment ago, an additional way for it to obtain its view — and you’ve probably already guessed what it is. When you first drag a View Controller object into the canvas in the nib editor, it already contains a view, hooked up and ready to act as its main view! The view controller, in fact, is portrayed in the canvas more or less as if it were this view. Thus, using the nib editor we can design the view controller’s main view’s interface in the view controller itself. Let’s try it:

  1. If you’ve been experimenting with the code in RootViewController.swift, remove any implementation of loadView or viewDidLoad.
  2. In Main.xib, find the plain vanilla View object in the Object library and drag it into the view controller object in the canvas. (This restores the view that we deleted earlier.) This view object automatically becomes the view controller’s view, and is drawn inside it in the canvas. Thus, you can now add further interface objects to this view.

Build and run. The interface you designed inside the view object inside the view controller object in Main.xib appears in the running app.

Tip

When a view controller is instantiated from a nib, your implementation of its init(nibName:bundle:) initializer is not called. If your nib-instantiated UIViewController subclass needs access to the view controller instance very early in its lifetime, override init(coder:) or awakeFromNib.

Storyboard-Instantiated View Controller

If you’ve ever used a storyboard, it will not have escaped your attention that the .xib file we constructed in the previous section, consisting of a view controller directly containing its view, looks a lot like a scene in a storyboard. That’s because, by default, this is the structure of a scene in a storyboard. Indeed, we are now ready to appreciate and understand how a storyboard works.

Each scene in a .storyboard file is rather like a .xib file containing a view controller nib object. A scene’s view controller is instantiated only when needed; the underlying mechanism is that the scene’s view controller is stored in a nib file in the built app, inside the .storyboardc bundle, and this nib file is loaded on demand and the view controller is instantiated from it, as we did in the previous section.

Moreover, by default, the view controller in a scene in a .storyboard file comes equipped with a view, which appears inside it in the canvas. You design the view and its subviews in the nib editor. When the app is built, each view controller’s view goes into a separate nib file, inside the .storyboardc bundle, and the view controller, once instantiated, loads its view from that nib file lazily, exactly as we did earlier.

In this way, a storyboard embodies the very same mechanisms we’ve already explored through .xib files. Even though a storyboard may appear, in the nib editor, to contain many view controllers and their main views, each view controller and each main view constitutes an individual nib, which is loaded on demand, when needed by the running app, just as if we had configured the project with multiple .xib files. Thus a storyboard combines the memory management advantages of .xib files, which are not loaded until they’re needed, and can be loaded multiple times to give additional instances of the same nib objects, with the convenience to you of being able to see and edit a lot of your app’s interface simultaneously in one place.

You don’t have to use the default scene structure in a storyboard. The default is that a view controller in a storyboard contains its view — but you can delete the view. If you do, then that view controller will obtain its view in any of the other ways we’ve already discussed: by an implementation of loadView in the code of that view controller class, or by loading an eponymous nib file, or even (if all of that fails) by creating a generic UIView.

Warning

There’s no way in a .storyboard file to specify as the source of a view controller’s view a .xib file with a different name from the view controller’s class; the nib editor lacks the NIB Name field in a view controller’s Attributes inspector when you’re working in a storyboard.

The Xcode app templates start with a single main storyboard called Main.storyboard, which is designated the app’s main storyboard by the Info.plist key “Main storyboard file base name” (UIMainStoryboardFile). Therefore, as the app launches, UIApplicationMain gets a reference to this storyboard by calling the UIStoryboard initializer init(name:bundle:), instantiates its initial view controller by calling instantiateInitialViewController, and makes that instance the window’s rootViewController.

If you add segues to your storyboard, then when one of those segues is performed — which can be configured to happen automatically in response to the user tapping an interface object — the destination view controller is automatically instantiated. In this way it is perfectly possible for a single storyboard to be the source of every view controller that your app will ever instantiate, and for all of that instantiation to take place automatically.

That doesn’t mean, however, that an app’s interface must be configured as a single monolithic main storyboard. It is perfectly possible to use a main storyboard for some view controllers and to instantiate other view controllers in code (perhaps getting their views from corresponding .xib files). You can also have different storyboards containing different view controllers. In Xcode 7, using multiple storyboards is easier than ever — you no longer have to load your ancillary storyboards manually, because a segue in one storyboard can lead through a storyboard reference to a view controller in another storyboard, which will be loaded automatically. (I’ll discuss storyboard references later in this chapter.)

You can get a reference to a storyboard either by calling the UIStoryboard initializer init(name:bundle:) or through the storyboard property of a view controller that has already been instantiated from that storyboard. With a storyboard instance in hand, a view controller can be instantiated from that storyboard in one of four ways:

  • At most one view controller in the storyboard is designated the storyboard’s initial view controller. To instantiate that view controller, call instantiateInitialViewController. The instance is returned.
  • A view controller in a storyboard can be assigned an arbitrary string identifier; this is its Storyboard ID in the Identity inspector. To instantiate that view controller, call instantiateViewControllerWithIdentifier:. The instance is returned.
  • A parent view controller in a storyboard may have immediate children, such as a UINavigationController and its initial child view controller. The nib editor will show a relationship connection between them. When the parent is instantiated (the source of the relationship), the initial children (the destination of the relationship) are instantiated automatically.
  • A view controller in a storyboard may be (or contain) the source of a segue whose destination is a future child view controller or a future presented view controller. When the segue is triggered and performed, it instantiates the new view controller.

I’ll go into much greater detail about storyboards and segues later in this chapter.

View Resizing

A view controller’s view is likely to be resized. Other views can be resized as well, of course, but quite often this is a direct consequence of the resizing of a view controller’s view, of which they are subviews. A view controller’s view is resized when it is put into the interface. It is resized when the app rotates. It may be resized in response to interface changes, such as when a navigation bar gets taller or shorter, appears or disappears. On the iPhone 6, it may even be resized because a change in the Display Zoom setting changes the effective size of the screen. Apple describes views and view controllers as adaptive to size changes.

Because a view controller is a controller, it is typically the locus of logic for helping the interface to cope with all this resizing. A view controller has properties and receives events connected to the resizing of its view, so that it can correctly dictate the arrangement of the interface.

View Size in the Nib Editor

When you design your interface in the nib editor, every view controller’s view has to be displayed at some definite size. But that size may not be the size at which the view will appear at runtime. If you design the interface only for the size you see in the nib editor, you can get a rude surprise when you actually run the app and the view appears at some other size. Failing to take account of this possibility is a common beginner mistake. On the contrary, you should assume that the size at which a view controller’s main view is portrayed in the nib editor canvas is probably not the size it will assume at runtime.

In the nib editor, you can display the view at the size of any actual device: for a storyboard, select the view controller (for a .xib file, select the top-level view) and choose a specific device from the Size pop-up menu under Simulated Metrics at the top of the Attributes inspector. You can also specify an orientation, as well as the presence or absence of interface elements that can affect layout (status bar, top bar, bottom bar).

But don’t forget that the specific size you see in the nib still may not reflect runtime reality. Suppose you design the interface for a view controller’s view sized like an iPhone 4s; then when the app loads on an iPhone 5, or one of the two iPhone 6 models, the view is a different size, and the interface as designed isn’t the interface you actually see. The Interface Builder Preview feature can be a big help here, allowing you to view your interface laid out for multiple devices simultaneously.

From this point of view, Xcode’s “Use Size Classes” option is a boon (see Chapter 1). By default, it shows a view controller’s main view as a square — a neutral shape that will never be encountered in real life. This serves as a reminder that the interface you are designing must adapt to a variety of real sizes when the app runs.

Bars and Underlapping

A view controller’s view will often have to adapt to the presence of bars at the top and bottom of the screen:

The status bar is underlapped
The status bar is transparent, so that the region of a view behind it is visible through it. The root view, and any other fullscreen view, must occupy the entire window, including the status bar area, the top of the view being visible behind the transparent status bar. You’ll want to design your view so that its top doesn’t contain any interface objects that will be overlapped by the status bar.
Top and bottom bars may be underlapped
The top and bottom bars displayed by a navigation controller (navigation bar, toolbar) or tab bar controller (tab bar) can be translucent. When they are, your view controller’s view is, by default, extended behind the translucent bar, underlapping it. Again, you’ll want to design your view so that this underlapping doesn’t conceal any of your view’s important interface.

The status bar may be present or absent. Top and bottom bars may be present or absent, and, if present, their height can change. How will your interface cope with such changes? The primary coping mechanism is the view controller’s layout guides.

Recall (from Chapter 1) that a view controller supplies two properties, its topLayoutGuide and its bottomLayoutGuide. The position of these guide objects moves automatically at runtime to reflect the view’s environment:

topLayoutGuide

The topLayoutGuide is positioned as follows:

  • If there is a status bar and no top bar, the topLayoutGuide is positioned at the bottom of the status bar.
  • If there is a top bar, the topLayoutGuide is positioned at the bottom of the top bar.
  • If there is no top bar and no status bar, the topLayoutGuide is positioned at the top of the view.
bottomLayoutGuide

The bottomLayoutGuide is positioned as follows:

  • If there is a bottom bar, the bottomLayoutGuide is positioned at the top of the bottom bar.
  • If there is no bottom bar, the bottomLayoutGuide is positioned at the bottom of the view.

The easiest way to involve the layout guides in your view layout is through autolayout and constraints. By constraining a view by its top to the bottom of the topLayoutGuide, or by its bottom to the top of the bottomLayoutGuide, you guarantee that the view will move when the layout guide moves. Such constraints are easy to form in the nib editor — they are the default. When you’re using the new iOS 9 anchor notation for creating a constraint in code, you’ll use the topLayoutGuide’s bottomAnchor and the bottomLayoutGuide’s topAnchor.

If you need actual numbers in order to perform layout-related calculations, a layout guide’s spacing from the corresponding edge of the view controller’s main view is reported by its length property. Note that viewDidLoad is too early to obtain a meaningful value; the earliest coherent opportunity is probably viewWillLayoutSubviews (I’ll discuss this event later). New in iOS 9, if you are constructing a constraint relative to the height of a layout guide, you can use its heightAnchor property.

Status bar visibility

The default behavior of the status bar is that it is present, except in landscape orientation on an iPhone, where it is absent. The root view controller, as I mentioned at the start of this chapter, gets a say in this behavior; it also determines the look of the status bar when present. Your UIViewController subclass can override these methods:

preferredStatusBarStyle
Your choices (UIStatusBarStyle) are .Default and .LightContent, meaning dark text and light text, respectively. Use light text for legibility if the view content underlapping the status bar is dark.
prefersStatusBarHidden
Return true to make the status bar invisible; return false to make the status bar visible, even in landscape orientation on an iPhone. (Return the result of a call to super to get the default behavior.)
childViewControllerForStatusBarStyle
childViewControllerForStatusBarHidden
Used to delegate the decision on the status bar style or visibility to a child view controller’s preferredStatusBarStyle or prefersStatusBarHidden. For example, a tab bar controller implements these methods to allow your view controller to decide the status bar style and visibility when your view controller’s view occupies the tab bar controller’s view. Thus, your view controller gets to make the decisions even though the tab bar controller is the root view controller.

You never call any of those methods yourself; they are called automatically when the view controller situation changes (including when the interface rotates). If you want them to be called immediately, because they are not being called when you need them to be, or because the situation has changed and a call to one of them would now give a different answer, call setNeedsStatusBarAppearanceUpdate on your view controller. If this call is inside an animation block, the animation of the change in the look of the status bar will have the specified duration. The character of the animation from visible to invisible (and vice versa) is set by your view controller’s implementation of preferredStatusBarUpdateAnimation; the value you return (UIStatusBarAnimation) can be .Fade, .Slide, or .None.

When you toggle the visibility of the status bar, the top layout guide will move up or down by 20 points. If your main view has subviews with constraints to the top layout guide, those subviews will move. If this happens when the main view is visible, the user will see this movement as a jump. That is probably not what you want. To prevent it, call layoutIfNeeded on your view in the same animation block in which you call setNeedsStatusBarAppearanceUpdate; your layout update will then be animated together with the change in status bar visibility. In this example, a button’s action method toggles the visibility of the status bar with smooth animation:

var hide = false
override func prefersStatusBarHidden() -> Bool {
    return self.hide
}
@IBAction func doButton(sender: AnyObject) {
    self.hide = !self.hide
    UIView.animateWithDuration(0.4, animations: {
        self.setNeedsStatusBarAppearanceUpdate()
        self.view.layoutIfNeeded()
    })
}

Extended layout

If your UIViewController’s parent is a navigation controller or tab bar controller, you can govern whether its view underlaps a top bar (navigation bar) or bottom bar (toolbar, tab bar) with these UIViewController properties:

edgesForExtendedLayout
A UIRectEdge. The default is .All, meaning that this view controller’s view will underlap a translucent top bar or a translucent bottom bar. The other extreme is .None, meaning that this view controller’s view won’t underlap top and bottom bars. Other possibilities are .Top (underlap translucent top bars only) and .Bottom (underlap translucent bottom bars only).
extendedLayoutIncludesOpaqueBars
If true, then if edgesForExtendedLayout permits underlapping of bars, those bars will be underlapped even if they are opaque. The default is false, meaning that only translucent bars are underlapped.

Resizing Events

A UIViewController receives events that notify it of pending view size changes. (Trait collections, size classes, and view layout events were discussed in Chapter 1.)

The following events are associated primarily with rotation of the interface; new in iOS 9, they are associated also with iPad multitasking (Chapter 9):

willTransitionToTraitCollection:withTransitionCoordinator:

Sent when the app is about to undergo a change in the trait collection (because the size classes will change). Common examples are rotation of 90 degrees on an iPhone, or a change between fullscreen and splitscreen on an iPad. This event is not sent on launch or when your view controller’s view is first embedded into the interface. If you override this method, call super.

UIViewController receives this event by virtue of adopting the UIContentContainer protocol.

viewWillTransitionToSize:withTransitionCoordinator:

Sent when the app is about to undergo rotation (even if the rotation turns out to be 180 degrees and the size won’t actually change) or an iPad multitasking size change. The new size is the first parameter; the old size is still available as self.view.bounds.size. This event is not sent on launch or when your view controller’s view is first embedded into the interface. If you override this method, call super.

UIViewController receives this event by virtue of adopting the UIContentContainer protocol.

traitCollectionDidChange:

Sent after the trait collection changes. The parameter is the old trait collection; the new trait collection is available as self.traitCollection. Sent after the trait collection changes, including on launch or when the trait collection is set for the first time (in which case the parameter will be nil).

UIViewController receives this event by virtue of adopting the UITraitEnvironment protocol.

(I’ll describe the use of the transitionCoordinator: parameter later in this chapter.)

In addition, a UIViewController receives these events related to the layout of its view:

updateViewConstraints
The view is about to be told to update its constraints (updateConstraints), including at application launch. If you override this method, call super.
viewWillLayoutSubviews
viewDidLayoutSubviews
These events surround the moment when the view is sent layoutSubviews, including at application launch.

In a situation where all these events are sent, the order is:

  • willTransitionToTraitCollection:withTransitionCoordinator:
  • viewWillTransitionToSize:withTransitionCoordinator:
  • updateViewConstraints
  • traitCollectionDidChange:
  • viewWillLayoutSubviews
  • viewDidLayoutSubviews

There is no guarantee that any of these events, if sent, will be sent exactly once.

Warning

Your view can be resized under many circumstances, such as the showing and hiding of a navigation bar that isn’t underlapped, without the viewWillTransitionToSize:... event being sent. Thus, to detect these changes, you’ll have to fall back on layout events such as viewWillLayoutSubviews. I regard this as a flaw in the iOS view controller event architecture.

Rotation

Your app can rotate, moving its top to correspond to a different edge of the device’s screen. In iOS 7 and before, this rotation was something of an illusion: the window remained pinned to the screen, and rotation was a matter of applying a transform to the root view and changing the bounds size to match the new orientation. In iOS 8 and later, however, the app really does rotate. Rotation expresses itself in two ways:

The status bar orientation changes

You can hear about this (though this will rarely be necessary) by way of these app delegate events and notifications:

  • application:willChangeStatusBarOrientation:duration: (and the corresponding UIApplicationWillChangeStatusBarOrientationNotification)
  • application:didChangeStatusBarOrientation: (and the corresponding UIApplicationDidChangeStatusBarOrientationNotification)

The current orientation (which is also the app’s current orientation) is available from the UIApplication as its statusBarOrientation; the app delegate methods also provide the other orientation (the one we are changing to or from, respectively) as the second parameter. Possible values (UIInterfaceOrientation) are:

  • .Portrait
  • .PortraitUpsideDown
  • .LandscapeLeft
  • .LandscapeRight

Two global convenience functions, UIInterfaceOrientationIsLandscape and UIInterfaceOrientationIsPortrait, take a UIInterfaceOrientation and return a Bool.

The view controller’s view is resized
The view controller receives events related to resizing, as I described in the preceding section. These may or may not include a change in the trait collection. Thus, the most general way to learn that rotation is taking place is through viewWillTransitionToSize:withTransitionCoordinator:. On the other hand, you are more likely to care about a 90 degree rotation on an iPhone than any other kind of rotation, and in that case willTransitionToTraitCollection:withTransitionCoordinator: applies.

There are two complementary uses for rotation:

Compensatory rotation
The app rotates to compensate for the orientation of the device, so that the app appears right way up with respect to how the user is holding the device. The challenge of compensatory rotation stems, quite simply, from the fact that the screen is not square. This means that if the app rotates 90 degrees, the interface no longer fits the screen, and must be changed to compensate.
Forced rotation
The app rotates when a particular view appears in the interface, or when the app launches, to indicate that the user needs to rotate the device in order to view the app the right way up. This is typically because the interface has been specifically designed, in the face of the fact that the screen is not square, to appear in one orientation (portrait or landscape).

In the case of the iPhone, no law says that your app has to perform compensatory rotation. Most of my iPhone apps do not do so; indeed, I have no compunction about doing just the opposite. My view controller views often look best in just one orientation (or one pair of opposed orientations, either just portrait or just landscape), and they stubbornly stay there regardless of how the user holds the device. A single app may contain view controller views that work best in different orientations; thus, my app forces the user to rotate the device differently depending on what view is being displayed. This is reasonable, because the iPhone is small and easily reoriented with a twist of the user’s wrist, and it has a natural right way up, especially because it’s a phone. (The iPod touch isn’t a phone, but the same argument works by analogy.)

On the other hand, Apple would prefer iPad apps to rotate to at least two opposed orientations (such as landscape with the button on the right and landscape with the button on the left), and preferably to all four possible orientations, so that the user isn’t restricted in how the device is held.

It’s fairly trivial to let your app rotate to two opposed orientations, because once the app is set up to work in one of them, it can work with no change in the other. But allowing a single interface to rotate between two orientations that are 90 degrees apart is trickier, because its dimensions must change — roughly speaking, its height and width are transposed — and this may require a change of layout and might even call for more substantial alterations, such as removal or addition of part of the interface. A good example is the behavior of Apple’s Mail app on the iPad: in landscape, the master pane and the detail pane appear side by side, but in portrait, the master pane is removed and must be summoned as a temporary overlay on top of the detail pane.

Permitting compensatory rotation

By default, when you create an Xcode project, the resulting app will perform compensatory rotation in response to the user’s rotation of the device. For an iPhone app, this means that the app can appear with its top at the top of the device or either of the two sides of the device. For an iPad app, this means that the app can assume any orientation.

If the default behavior isn’t what you want, it is up to you to change it. There are three levels at which you can make changes:

  • The app itself, in its Info.plist, may declare once and for all every orientation the interface will ever be permitted to assume. It does this under the “Supported interface orientations” key, UISupportedInterfaceOrientations (supplemented, for a universal app, by “Supported interface orientations (iPad),” UISupportedInterfaceOrientations~ipad). These keys can also be set through checkboxes when you edit the app target, in the General tab.
  • The app delegate may implement the application:supportedInterfaceOrientationsForWindow: method, returning a bitmask listing every orientation the interface is permitted to assume. This list overrides the Info.plist settings. Thus, the app delegate can do dynamically what the Info.plist can do only statically. application:supportedInterfaceOrientationsForWindow: is called at least once every time the device rotates.
  • The top-level view controller — that is, the root view controller, or a view controller presented fullscreen — may implement supportedInterfaceOrientations, returning a bitmask listing a set of orientations that intersects the set of orientations permitted by the app or the app delegate. The resulting intersection will then be the set of orientations permitted at that moment. This intersection must not be empty; if it is, your app will crash (with a useful message: “Supported orientations has no common orientation with the application”). supportedInterfaceOrientations is called at least once every time the device rotates.

    The top-level view controller can also implement shouldAutorotate. This method returns a Bool, and the default is true. shouldAutorotate is called at least once every time the device rotates; if it returns false, the interface will not rotate to compensate at this moment, and supportedInterfaceOrientations is not called.

Warning

Built-in parent view controllers, when they are top-level view controller, do not automatically consult their children about rotation. If your view controller is a child view controller of a UITabBarController or a UINavigationController, it has no direct say in how the app rotates. Those parent view controllers, however, do consult their delegates about rotation, as I’ll explain later.

A UIViewController class method attemptRotationToDeviceOrientation prompts the runtime to do immediately what it would do if the user were to rotate the device, namely to walk the three levels I’ve just described and, if the results permit rotation of the interface to match the current device orientation, to rotate the interface. This would be useful if, say, your view controller had previously returned false from shouldAutorotate, but is now for some reason prepared to return true and wants to be asked again, immediately.

The bitmask you return from application:supportedInterfaceOrientationsForWindow: or supportedInterfaceOrientations is a UIInterfaceOrientationMask. It may be one of these values, or multiple values combined:

  • .Portrait
  • .LandscapeLeft
  • .LandscapeRight
  • .PortraitUpsideDown
  • .Landscape (a combination of .Left and .Right)
  • .All (a combination of .Portrait, .UpsideDown, .Left, and .Right)
  • .AllButUpsideDown (a combination of .Portrait, .Left, and .Right)

For example:

override func supportedInterfaceOrientations()
    -> UIInterfaceOrientationMask {
        return .Portrait
}

If your code needs to know the current physical orientation of the device (as opposed to the current orientation of the app), it can ask the device:

let orientation = UIDevice.currentDevice().orientation

Possible results (UIDeviceOrientation) are .Unknown, .Portrait, and so on. Global convenience functions UIDeviceOrientationIsPortrait and UIDeviceOrientationIsLandscape take a UIDeviceOrientation and return a Bool. By the time you get a rotation-related event, the device’s orientation has already changed.

Warning

In iOS 9, because of the new iPad multitasking architecture (Chapter 9), an iPad app that doesn’t permit all four orientations must include in its Info.plist the UIRequiresFullScreen key with a value of YES. Moreover, if the app’s Info.plist does not include UIRequiresFullScreen and does permit all four orientations, then supportedInterfaceOrientations and shouldAutorotate are never called, presumably because the answer is known in advance.

Initial orientation

I’ve talked about how to determine what orientations your app can support in the course of its lifetime; but what about its initial orientation, the very first orientation your app will assume when it launches?

On the iPad, an app has no fixed initial orientation. iPad apps are supposed to be more or less orientation-agnostic, so the app will launch into whatever permitted orientation is closest to the device’s current orientation at launch time.

On the iPhone, in iOS 9, the app’s initial orientation is always portrait orientation if your Info.plist includes portrait orientation (UIInterfaceOrientationPortrait). If your initial root view controller also permits portrait orientation, but the user is holding the device in a landscape orientation that is also permitted, then the app will launch normally, correctly, and fully into portrait orientation, appearing to the user in portrait, and will then almost immediately afterward visibly rotate to landscape. This is essentially no different than if the user had been holding the device in portrait orientation while launching your app and then rotated it to landscape later.

If your Info.plist does not include portrait orientation, then the app launches directly into landscape. Which landscape orientation the app will prefer is dictated by the order in which the landscape orientations are listed in the Info.plist. If, say, the first listing is “Landscape (left home button)” (UIInterfaceOrientationLandscapeLeft), but the user is holding the device in landscape with the Home button at the right, then the app will launch directly into landscape upside down (from the user’s point of view), and will then visibly rotate 180 degrees.

If your Info.plist does include portrait orientation but your initial root view controller does not permit portrait orientation, then in iOS 9 the app will start out in portrait orientation but will rotate to landscape (with a trait collection transition, but no size transition) before performing layout and appearing to the user. In this situation, the order of orientations listed in the Info.plist makes no difference: the app will initially appear to the user as if the home button were on the right, and if in fact it is on the left, the app will then visibly rotate 180 degrees. This behavior is a change from how things worked in iOS 8, where the order of orientations in the Info.plist mattered; I regard it as somewhat incoherent. A possible workaround is to exclude portrait orientation from the Info.plist but include it in your implementation of application:supportedInterfaceOrientationsForWindow:.

Initial layout

If you have code that performs or contributes to the initial construction and layout of your app’s interface, where should that code go? What events your root view controller will receive at launch differs depending on various rotation-related factors mentioned in the previous section, but at a minimum, you can certainly count on the following:

  • viewDidLoad
  • traitCollectionDidChange:
  • viewWillLayoutSubviews

There is a natural temptation to perform initial layout-related tasks in viewDidLoad, just because it’s so convenient. It is called exactly once as early as possible in the life of the view controller’s view; that’s why it is a conventional locus for preparation of the interface (as well as other view controller initializations). However, keep in mind that at the time viewDidLoad is called, the view has been loaded, but it has not yet been inserted into the interface! Thus it has not yet been fully resized for the first time. A layout-related task that depends upon the absolute dimensions of the view is therefore destined to generate erroneous results. As long as you don’t rely on a knowledge of the final absolute dimensions of your view — in particular, if your initial layout configuration involves autolayout — viewDidLoad is a reasonable place to do it.

In this (completely artifical) example, our goal is to insert a small black square at the top center of the interface. This code could be a mistake:

override func viewDidLoad() {
    super.viewDidLoad()
    let square = UIView(frame:CGRectMake(0,0,10,10))
    square.backgroundColor = UIColor.blackColor()
    square.center = CGPointMake(self.view.bounds.midX,5) // top center?
    self.view.addSubview(square)
}

That might work, but then again it might not. On an iPhone, under certain circumstances, at the time viewDidLoad is called, the width (and hence the midX) of self.view might not yet have the value it will have when the interface settles down, and our square will be misplaced. This still might not matter, however, if our square also has the ability to compensate for subsequent rotation and resizing. It might be enough to add autoresizing:

override func viewDidLoad() {
    super.viewDidLoad()
    let square = UIView(frame:CGRectMake(0,0,10,10))
    square.backgroundColor = UIColor.blackColor()
    square.center = CGPointMake(self.view.bounds.midX,5)
    square.autoresizingMask =
        [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleBottomMargin]
    self.view.addSubview(square)
}

The best approach is to use autolayout:

override func viewDidLoad() {
    super.viewDidLoad()
    let square = UIView()
    square.backgroundColor = UIColor.blackColor()
    self.view.addSubview(square)
    square.translatesAutoresizingMaskIntoConstraints = false
    let side : CGFloat = 10
    var con = [NSLayoutConstraint]()
    con.appendContentsOf([
        square.widthAnchor.constraintEqualToConstant(side),
        square.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor)
    ])
    con.appendContentsOf(
        NSLayoutConstraint.constraintsWithVisualFormat("V:|[square(side)]",
            options:[], metrics:["side":side],
            views:["square":square]))
    NSLayoutConstraint.activateConstraints(con)
}

Our view (self.view) may proceed to be resized further as the interface settles down, but the small black square will still be positioned correctly, because the constraints determine that position relative to whatever the size of our view may be.

If we decide to perform layout initializations in traitCollectionDidChange: or viewWillLayoutSubviews, we face the quandary that these methods can be called multiple times over the lifetime of a view controller. To ensure that we perform initializations just once, we can make use of a Bool property flag:

var viewInitializationDone = false
override func viewWillLayoutSubviews() {
    if !self.viewInitializationDone {
        self.viewInitializationDone = true // ensure we do this just once
        // perform initializations here
    }
}

Responding to rotation

When your app rotates 90 degrees, your view controller’s main view may have its height and width bounds dimensions effectively transposed. This is an extraordinarily dramatic and extreme resizing, especially on an iPhone. Your interface may need to be rearranged, perhaps quite heavily, to cope with this change in your view’s size. You may need not only to move and resize views but even to insert or remove views.

In many cases, autoresizing or (more likely) autolayout will handle the situation — and you can get a serious boost from conditional constraints and the ability to configure things in the nib editor so that views and constraints are removed and inserted automatically in response to a trait collection change (see Chapter 1).

Sometimes, however, code is needed to perform or supplement the rearrangement of your interface at rotation time. Such code is not at all easy to implement. In iOS 7 and before, rotation events (with rotate in their names) arrived on both iPad and iPhone to let you know what’s happening. But in iOS 8, those rotation events were deprecated, and were replaced with willTransitionToTraitCollection:... and viewWillTransitionToSize:.... Unfortunately those new events do not arrive in a consistently helpful way; in particular, the former event is not sent when an iPad rotates, because iPad rotation does not cause any change in the trait collection.

Rotation is animated, and you’ll probably want your layout changes to harmonize with and participate in that animation. This is where the transitionCoordinator: parameter comes in. It is (not surprisingly) a transition coordinator; in particular, it’s an object adopting the UIViewControllerTransitionCoordinator protocol, which means that it is reponsible for governing a runtime animation (in this case, the rotation animation), and that it implements this method:

animateAlongsideTransition:completion:

Takes an animation block and a completion block. The animation you supply is incorporated into the transition coordinator’s animation. Returns a Bool, informing you in case your commands couldn’t be animated.

Both blocks receive as parameter a context object implementing the UIViewControllerTransitionCoordinatorContext protocol. This object, among other things, has a targetTransform method that you can call to learn how far and in what direction the interface is rotating. (The transition coordinator, too, is such a context object.) In particular, if rot is the result of calling targetTransform, the rotation is 180 degrees — the thing you’re most likely to want to know — if and only if both rot.b and rot.c are 0.

(Other methods implemented by the transition coordinator and context object are irrelevant to rotation; I’ll discuss them later in this chapter, in connection with custom view controller transitions.)

In this (completely artificial) example, I’ll specify that our interface should display a large green rectangle occupying the left side of the screen if the device is in landscape orientation, but not if the device is in portrait orientation. Let’s stipulate that this will be an iPhone app; thus, I can be notified of rotation by implementing willTransitionToTraitCollection:withTransitionCoordinator:, and I can test which way we’re about to rotate by examining the new trait collection’s vertical size class. I’ll animate the green rectangle on and off the screen in coordination with the rotation animation:

lazy var greenRect : UIView = self.makeGreenRect()
func makeGreenRect() -> UIView {
    var f = self.view.bounds
    if self.traitCollection.verticalSizeClass != .Compact {
        (f.size.width, f.size.height) = (f.size.height, f.size.width)
    }
    f.size.width /= 3.0
    f.origin.x = -f.size.width
    let gr = UIView(frame:f)
    gr.backgroundColor = UIColor.greenColor()
    return gr
}
override func willTransitionToTraitCollection(
    newCollection: UITraitCollection,
    withTransitionCoordinator coordinator:
    UIViewControllerTransitionCoordinator) {
        super.willTransitionToTraitCollection(newCollection,
            withTransitionCoordinator: coordinator)
        let v = self.greenRect
        var newFrameOriginX = v.frame.origin.x
        if newCollection.verticalSizeClass == .Compact { // landscape
            if v.superview == nil {
                self.view.addSubview(v)
                newFrameOriginX = 0
            }
        } else { // portrait
            if v.superview != nil {
                newFrameOriginX = -v.frame.size.width
            }
        }
        coordinator.animateAlongsideTransition({
            _ in
            v.frame.origin.x = newFrameOriginX // animate!
            }, completion: {
                _ in
                if newCollection.verticalSizeClass != .Compact {
                    self.greenRect.removeFromSuperview()
                }
            })
}

As usual, a constraint-based solution would be more robust. This is a good use of the technique I described in Chapter 1, where we prepare two sets of constraints and swap them in and out. I won’t even bother to remove the green rectangle from the interface; I’ll add it once and for all as I configure the view initially, and just slide it onscreen and offscreen as needed:

var greenRectConstraintsOnscreen : [NSLayoutConstraint]!
var greenRectConstraintsOffscreen : [NSLayoutConstraint]!
override func viewDidLoad() {
    super.viewDidLoad()
    let gr = UIView()
    gr.translatesAutoresizingMaskIntoConstraints = false
    gr.backgroundColor = UIColor.greenColor()
    self.view.addSubview(gr)
    var c = [NSLayoutConstraint]()
    // "g.r. is pinned to top and bottom of superview"
    c.appendContentsOf(
        NSLayoutConstraint.constraintsWithVisualFormat("V:|[gr]|",
            options:[], metrics:nil, views:["gr":gr]))
    // "g.r. is 1/3 the width of superview"
    c.append(
        gr.widthAnchor.constraintEqualToAnchor(self.view.widthAnchor,
            multiplier: 1.0/3.0))
    // "onscreen, g.r.'s left is pinned to superview's left"
    let marrOn =
        NSLayoutConstraint.constraintsWithVisualFormat("H:|[gr]",
            options:[], metrics:nil, views:["gr":gr])
    // "offscreen, g.r.'s right is pinned to superview's left"
    let marrOff = [
        gr.trailingAnchor.constraintEqualToAnchor(self.view.leadingAnchor)!
    ]
    self.greenRectConstraintsOnscreen = marrOn
    self.greenRectConstraintsOffscreen = marrOff
    // start out offscreen!
    c.appendContentsOf(marrOff)
    NSLayoutConstraint.activateConstraints(c)
}
override func willTransitionToTraitCollection(
    newCollection: UITraitCollection,
    withTransitionCoordinator coordinator:
    UIViewControllerTransitionCoordinator) {
        super.willTransitionToTraitCollection(newCollection,
            withTransitionCoordinator: coordinator)
        NSLayoutConstraint.deactivateConstraints(
            self.greenRectConstraintsOnscreen)
        NSLayoutConstraint.deactivateConstraints(
            self.greenRectConstraintsOffscreen)
        if newCollection.verticalSizeClass == .Compact {
            NSLayoutConstraint.activateConstraints(
                self.greenRectConstraintsOnscreen)
        } else {
            NSLayoutConstraint.activateConstraints(
                self.greenRectConstraintsOffscreen)
        }
    }

The movement of the green rectangle is animated as the interface rotates, because any constraint-based layout performed as the interface rotates is animated!

For this particular example, an even more elegant solution is possible: the entire change of interface can be configured in the nib, using conditional constraints. The details are left as an exercise for the reader.

But none of those approaches will work if this app runs natively on an iPad! When an iPad rotates, there is no change of trait collection. Thus, willTransitionToTraitCollection:withTransitionCoordinator: will never be sent; we can’t learn how we are oriented by looking at the trait collection; and conditional constraints configured in the nib won’t operate. Instead, we have to concentrate on our main view’s size. We can implement viewWillTransitionToSize:withTransitionCoordinator: to detect rotation, and we can learn our new orientation by comparing the new size’s height to its width. But this event won’t be sent if we launch into landscape on the iPad, so we’ll have to factor out our constraint-swapping code and call it from viewDidLoad as well:

override func viewDidLoad() {
    super.viewDidLoad()
    let gr = UIView()
    // ... create constraints as before ...
    self.adjustInterfaceForSize(self.view.bounds.size)
}
override func viewWillTransitionToSize(size: CGSize,
    withTransitionCoordinator coordinator:
    UIViewControllerTransitionCoordinator) {
        super.viewWillTransitionToSize(size,
            withTransitionCoordinator: coordinator)
        if size != self.view.bounds.size {
            self.adjustInterfaceForSize(size)
        }
}
func adjustInterfaceForSize(size: CGSize) {
    NSLayoutConstraint.deactivateConstraints(
        self.greenRectConstraintsOnscreen)
    NSLayoutConstraint.deactivateConstraints(
        self.greenRectConstraintsOffscreen)
    if size.width > size.height {
        NSLayoutConstraint.activateConstraints(
            self.greenRectConstraintsOnscreen)
    } else {
        NSLayoutConstraint.activateConstraints(
            self.greenRectConstraintsOffscreen)
    }
}

Presented View Controller

Back when the only iOS device was an iPhone, a presented view controller was called a modal view controller. When a modal view controller was summoned (presented), the root view controller remained in place, but its view was taken out of the interface and the modal view controller’s view was used instead. Thus, this was the simplest way to replace the entire interface with a different interface.

You can see why this configuration was characterized as “modal.” The presented view controller’s view has, in a sense, blocked access to the “real” view, the root view controller’s view. The user is forced to work in the presented view controller’s view, until that view is “dismissed” and the “real” view is visible again — similar to a modal dialog in a desktop application, where the user can’t do anything else but work in the dialog as long as it is present. A presented view controller’s view often reinforces this analogy with obvious dismissal buttons with titles like Save, Done, or Cancel.

The color picker view in my own Zotz! app is a good example (Figure 6-7); this is an interface that says, “You are now configuring a color, and that’s all you can do; change the color or cancel, or you’ll be stuck here forever.” The user can’t get out of this view without tapping Cancel or Done, and the view that the user was previously using is visible as a blur behind this view, waiting for the user to return to it.

A modal view
Figure 6-7. A modal view

Figure 6-5, from my Latin flashcard app, is another example of a presented view. It has a Cancel button, and the user is in a special “mode,” performing a drill exercise rather than scrolling through flashcards.

Nevertheless, the “modal” characterization is not always apt. A presented view controller might be no more than a technique that you, the programmer, have used to alter the interface; the user needn’t be conscious of this. A presented view controller’s view may have a complex interface; it may have child view controllers; it may present yet another view controller; it may take over the interface permanently, with the user never returning to the interface that it replaced.

Furthermore, the range of ways in which a presented view controller’s view can be displayed now goes far beyond merely replacing the root view controller’s view. For example:

  • Instead of replacing the entire interface, a presented view controller’s view can replace a subview within the existing interface. (This ability was originally confined to the iPad alone; starting in iOS 8, it became available on the iPhone as well.)
  • A presented view controller’s view may cover the existing interface only partially; the existing interface is never removed. (This, too, was originally an iPad-only feature; it became possible on the iPhone starting in iOS 7.)

Presenting a View

The two key methods for presenting and dismissing a view are:

presentViewController:animated:completion:

To make a view controller present another view controller, you send the first view controller this message, handing it the second view controller, which you will probably instantiate for this very purpose. (The first view controller is very typically self.)

We now have two view controllers that stand in the relationship of being one another’s presentingViewController and presentedViewController respectively. The presented view controller is retained, and its view effectively replaces or covers the presenting view controller’s view in the interface (I’ll talk later about ways to refine that arrangement).

dismissViewControllerAnimated:completion:
The “presented” state of affairs described in the previous paragraph persists until the presenting view controller is sent this message. The presented view controller’s view is then removed from the interface, the original interface is restored, and the presented view controller is released; it will thereupon typically go out of existence, together with its view, its child view controllers and their views, and so on.

As the view of the presented view controller appears, and again when it is dismissed, there’s an option for animation to be performed as the transition takes place (the animated: argument, a Bool). The completion: parameter, which can be nil, lets you supply a block of code to be run after the transition (including the animation) has occurred. I’ll talk later about how to govern the nature of the animation.

The presenting view controller (the presented view controller’s presentingViewController) is not necessarily the view controller to which you sent presentViewController:animated:completion:. It will help if we distinguish three roles that view controllers can play in presenting a view controller:

Presented view controller
The view controller specified as the first argument to presentViewController:animated:completion:.
Original presenter

The view controller to which presentViewController:animated:completion: was sent. Apple sometimes refers to this view controller as the source; “original presenter” is my own term.

The presented view controller is set as the original presenter’s presentedViewController.

Presenting view controller

The presented view controller’s presentingViewController. This is the view controller whose view is replaced or covered by the presented view controller’s view. By default, it is the view controller whose view is the entire interface — namely, either the root view controller or an already existing presented view controller. It might not be the same as the original presenter.

The presented view controller is set as the presenting view controller’s presentedViewController. Thus, the presented view controller might be the presentedViewController of two different view controllers.

The receiver of dismissViewControllerAnimated:completion: may be any of those three objects; the runtime will use the linkages between them to transmit the necessary messages up the chain on your behalf to the presentingViewController.

You can test whether a view controller’s presentedViewController or presentingViewController is nil to learn whether view presentation is occurring. For example, a view controller whose presentingViewController is nil is not a presented view controller at this moment.

A view controller can have at most one presentedViewController. If you send presentViewController:animated:completion: to a view controller whose presentedViewController isn’t nil, nothing will happen and the completion handler is not called (and you’ll get a warning from the runtime).

However, a presented view controller can itself present a view controller, so there can be a chain of presented view controllers. If you send dismissViewControllerAnimated:completion: to a view controller in the middle of a presentation chain — a view controller that has both a presentingViewController and a presentedViewController — then its presentedViewController is dismissed.

If you send dismissViewControllerAnimated:completion: to a view controller whose presentedViewController is nil and that has no presentingViewController, nothing will happen (not even a warning in the console), and the completion: handler is not called.

Let’s make one view controller present another. We could do this simply by connecting one view controller to another in a storyboard with a modal segue, but I don’t want you to do that: a modal segue calls presentViewController:animated:completion: for you, whereas I want you to call it yourself.

So start with an iPhone project made from the Single View Application template. This contains one view controller class, called ViewController. Our first move must be to add a second view controller class, an instance of which will function as the presented view controller:

  1. Choose File → New → File and specify iOS → Source → Cocoa Touch Class. Click Next.
  2. Name the class SecondViewController, make sure it is a subclass of UIViewController, and check the XIB checkbox so that we can design this view controller’s view quickly and easily in a nib. (We could use the storyboard instead, but that would not be as educational.) Click Next.
  3. Confirm the folder, group, and app target membership, and click Create.
  4. Edit SecondViewController.xib, and do something there to make the view distinctive, so that you’ll recognize it when it appears; for example, give it a red background color.
  5. We need a way to trigger the presentation of SecondViewController. Edit Main.storyboard and add a button to the ViewController’s view’s interface. Connect that button to an action method in ViewController.swift; let’s call it doPresent:.
  6. In ViewController.swift, write the code for doPresent:, as follows:

    @IBAction func doPresent(sender:AnyObject?) {
        let svc = SecondViewController(
            nibName: "SecondViewController", bundle: nil)
        self.presentViewController(svc, animated:true, completion:nil)
    }

Run the project. In ViewController’s view, tap the button. SecondViewController’s view slides into place over ViewController’s view.

In our lust for instant gratification, we have neglected to provide a way to dismiss the presented view controller. If you’d like to do that, edit SecondViewController.xib, put a button into SecondViewController’s view, and connect it to an action method in SecondViewController.swift:

@IBAction func doDismiss(sender:AnyObject?) {
    self.presentingViewController!.dismissViewControllerAnimated(
        true, completion: nil)
}

Run the project. You can now alternate between ViewController’s view and SecondViewController’s view, presenting and dismissing in turn. Go ahead and play for a while with your exciting new app; I’ll wait.

Communication With a Presented View Controller

In real life, it is highly probable that the original presenter will have additional information to impart to the presented view controller as the latter is created and presented, and that the presented view controller will want to pass information back to the original presenter as it is dismissed. Knowing how to arrange this exchange of information is very important.

Passing information from the original presenter to the presented view controller is usually easy, because the original presenter typically has a reference to the presented view controller before the latter’s view appears in the interface. For example, suppose the presented view controller has a public data property. Then the original presenter can easily set this property:

@IBAction func doPresent(sender:AnyObject?) {
    let svc = SecondViewController(
        nibName: "SecondViewController", bundle: nil)
    svc.data = "This is very important data!" // *
    self.presentViewController(svc, animated:true, completion:nil)
}

Indeed, if you’re calling presentViewController:animated:completion: explicitly like this, you might even give your SecondViewController a designated initializer that accepts — and thus requires — this data. In my Latin vocabulary app, for example, I’ve given DrillViewController a designated initializer init(data:) precisely so that whoever creates it must pass it the data it will need to do its job while it exists.

Passing information back from the presented view controller to the original presenter is a more interesting problem. The presented view controller will need to know who the original presenter is, but it doesn’t automatically have a reference to it (the original presenter, remember, is not necessarily the same as the presentingViewController). Moreover, the presented view controller will need to know the signature of some method, implemented by the original presenter, which it can call in order to hand over the information — and this needs to work regardless of the original presenter’s class.

The standard solution is to use delegation, as follows:

  1. The presented view controller defines a protocol declaring a method that the presented view controller wants to call before it is dismissed.
  2. The original presenter conforms to this protocol: it declares adoption of the protocol, and it implements the required method.
  3. The presented view controller provides a means whereby it can be handed a reference to an object conforming to this protocol. Think of that reference as the presented view controller’s delegate. Very often, this will be a property — perhaps called delegate! — typed as the protocol. (Such a property should be weak, since an object usually has no business retaining its delegate.)
  4. As the original presenter creates and configures the presented view controller, it hands the presented view controller a reference to itself, in its role as adopter of the protocol, by assigning itself as the presented view controller’s delegate.

This sounds elaborate, but with practice you’ll find yourself able to implement it very quickly. And you can see why it works: because its delegate is typed as the protocol, the presented view controller is guaranteed that this delegate, if it has one, implements the method declared in the protocol. Thus, the desired communication from the presented view controller to whoever configured and created it is assured.

Let’s modify our example to embody this architecture. First, edit SecondViewController.swift to look like this:

protocol SecondViewControllerDelegate : class {
    func acceptData(data:AnyObject!)
}
class SecondViewController : UIViewController {
    var data : AnyObject?
    weak var delegate : SecondViewControllerDelegate?
    @IBAction func doDismiss(sender:AnyObject?) {
        self.delegate?.acceptData("Even more important data!")
    }
}

It is now ViewController’s job to adopt the SecondViewControllerDelegate protocol, and to set itself as the SecondViewController’s delegate. When the delegate method is called, ViewController will be handed the data, and it should then dismiss the SecondViewController:

class ViewController : UIViewController, SecondViewControllerDelegate {
    @IBAction func doPresent(sender:AnyObject?) {
        let svc = SecondViewController(
            nibName: "SecondViewController", bundle: nil)
        svc.data = "This is very important data!"
        svc.delegate = self // *
        self.presentViewController(svc, animated:true, completion:nil)
    }
    func acceptData(data:AnyObject!) {
        // do something with data here
        self.dismissViewControllerAnimated(true, completion: nil)
    }
}

That is a perfectly satisfactory implementation, and we could stop at this point. For completeness, I’ll just show a possible variation. You might object that too much responsibility rests upon the original presenter (the delegate): it is sent the data and then it must also dismiss the presented view controller. Surely the presented view controller should hand back any data and should then dismiss itself (as in the preceding section). Even better, the presented view controller should hand back any data automatically, regardless of how it is dismissed.

We can arrange that by putting all the responsibility on the presented view controller. First, edit ViewController’s acceptData: so that it accepts the data and no more (it no longer performs the dismissal). Second, back in SecondViewController, we will implement both the task of dismissal and the task of handing back the data, separately. To make the latter task automatic, SecondViewController will arrange to hear about its own dismissal by implementing viewWillDisappear: (discussed later in this chapter), which will then call acceptData: to ensure that the data is handed across. There is more than one reason why viewWillDisappear: might be called; we can ensure that this really is the moment of our own dismissal by calling isBeingDismissed. Here is what SecondViewController looks like now:

protocol SecondViewControllerDelegate : class {
    func acceptData(data:AnyObject!)
}
class SecondViewController : UIViewController {
    var data : AnyObject?
    weak var delegate : SecondViewControllerDelegate?
    @IBAction func doDismiss(sender:AnyObject?) {
        self.presentingViewController!.dismissViewControllerAnimated(
            true, completion: nil)
    }
    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        if self.isBeingDismissed() {
            self.delegate?.acceptData("Even more important data!")
        }
    }
}

If you’re using a storyboard, things are a bit different. You don’t have access to the presented view controller at the moment of creation; instead, in the original presenter (the source of the segue) you implement prepareForSegue:sender: as a moment when the original presenter and the presented view controller will meet, and the former can hand across any needed data, set itself as delegate, and so forth. If you dismiss the presented view controller automatically by way of an unwind segue, the same is true in reverse: the presented view controller calls the delegate method in its own prepareForSegue:sender:. I’ll give more details later in this chapter.

Presented View Animation

When a view is presented and later when it is dismissed, a simple animation can be performed, according to whether the animated: parameter of the corresponding method is true. There are a few different built-in animation styles (modal transition styles) to choose from.

Note

Instead of choosing a simple built-in modal transition style, you can supply your own animation, as I’ll explain later in the chapter.

Your choice of built-in animation style is not passed as a parameter when presenting or dismissing a view controller; rather, it is attached beforehand to a presented view controller as its modalTransitionStyle property. This value can be set in code or in the nib editor. Your choices (UIModalTransitionStyle) are:

.CoverVertical (the default)
The presented view slides up from the bottom to cover the presenting view on presentation and down to reveal the presenting view on dismissal. “Bottom” is defined differently depending on the orientation of the device and the orientations the view controllers support.
.FlipHorizontal

The view flips on the vertical axis as if the two views were the front and back of a piece of paper. The “vertical axis” is the device’s long axis, regardless of the app’s orientation.

This animation style provides one of those rare occasions where the user may directly glimpse the window behind the transitioning views. You may want to set the window’s background color appropriately.

.CrossDissolve
The views remain stationary, and one fades into the other.
.PartialCurl

The first view curls up like a page in a notepad to expose most of the second view, but remains covering the top-left region of the second view. Thus there must not be any important interface in that region, as the user will not be able to see it.

If the user clicks on the curl, dismissViewControllerAnimated:completion: is called on the original presenter. That’s convenient, but make sure it doesn’t disrupt communication between your view controllers; this is another reason for factoring out any final handing back of information from the presented view controller into its viewWillDisappear:, as I did in the previous section.

Warning

In iOS 8 and iOS 9, after the Partial Curl animation, the curled page is not present, but tapping where it would be dismisses the presented view controller. That’s confusing, not to say incoherent, and is presumably a bug; this style should be avoided until it is fixed.

Presentation Styles

By default, the presented view controller’s view occupies the entire screen, completely replacing that of the presenting view controller. But you can choose from a few other built-in options expressing how the presented view controller’s view should cover the screen (modal presentation styles).

Note

Instead of choosing a simple built-in modal presentation style, you can customize the presentation to place the presented view controller’s view anywhere you like, as I’ll explain later in this chapter.

To choose a presentation style, set the presented view controller’s modalPresentationStyle property. This value can be set in code or in the nib editor. Your choices (UIModalPresentationStyle) are:

.FullScreen
The default. The presenting view controller is the root view controller or a fullscreen presented view controller, and its view — meaning the entire interface — is replaced.
.OverFullScreen
Similar to .FullScreen, but the presenting view controller’s view is not replaced; instead, it stays where it is, possibly being visible during the transition, and remaining visible behind the presented view controller’s view if the latter has some transparency.
.PageSheet
Similar to .FullScreen, but in portrait orientation on the iPad it’s a little shorter (leaving a gap behind the status bar), and in landscape orientation on the iPad and the iPhone 6 Plus it’s also narrower, with the presenting view controller’s view remaining partially visible (and dimmed) behind it. Treated as .FullScreen on the iPhone (including the iPhone 6 Plus in portrait).
.FormSheet
Similar to .PageSheet, but on the iPad it’s even smaller, allowing the user to see more of the presenting view controller’s view behind it. As the name implies, this is intended to allow the user to fill out a form (Apple describes this as “gathering structured information from the user”). On the iPhone 6 Plus in landscape, indistinguishable from .PageSheet. Treated as .FullScreen on the iPhone (including the iPhone 6 Plus in portrait).
.CurrentContext
The presenting view controller can be any view controller, such as a child view controller. The presented view controller’s view replaces the presenting view controller’s view, which may have been occupying only a portion of the screen. I’ll explain in a moment how to specify the presenting view controller.
.OverCurrentContext
Like .CurrentContext, but the presented view controller’s view covers the presenting view controller’s view rather than replacing it. Again, this may mean that the presented view controller’s view now covers only a portion of the screen. .OverCurrentContext will often be a better choice than .CurrentContext, because some subviews don’t behave well when automatically removed from their superview and restored later.

When the presented view controller’s modalPresentationStyle is .CurrentContext or .OverCurrentContext, a decision has to be made by the runtime as to what view controller should be the presenting view controller. This will determine what view will be replaced or covered by the presented view controller’s view. The decision involves another UIViewController property, definesPresentationContext (a Bool), and possibly still another UIViewController property, providesPresentationContextTransitionStyle. Here’s how the decision operates:

  1. Starting with the original presenter (the view controller to which presentViewController:animated:completion: was sent), we walk up the chain of parent view controllers, looking for one whose definesPresentationContext property is true. If we find one, that’s the one; it will be the presentingViewController, and its view will be replaced or covered by the presented view controller’s view.

    (If we don’t find one, things work as if the presented view controller’s modalPresentationStyle had been .FullScreen.)

  2. If, during the search just described, we find a view controller whose definesPresentationContext property is true, we look to see if that view controller’s providesPresentationContextTransitionStyle property is also true. If so, that view controller’s modalTransitionStyle is used for this transition animation, instead of using the presented view controller’s modalTransitionStyle.

To illustrate, I need a parent–child view controller arrangement to work with. This chapter hasn’t yet discussed any parent view controllers in detail, but the simplest is UITabBarController, which I discuss in the next section, and it’s easy to create a working app with a UITabBarController-based interface, so that’s the example I’ll use.

Start with the Tabbed Application project template. As in the previous example, I want us to create and present the presented view controller manually, rather than letting the storyboard do it automatically; so make a new view controller class with an accompanying .xib file, to use as a presented view controller — call it ExtraViewController. In ExtraViewController.xib, give the view a distinctive background color, so you’ll recognize it when it appears.

In the storyboard, put a button in the First View Controller view, and connect it to an action method in FirstViewController.swift that summons the new view controller as a presented view controller:

@IBAction func doPresent(sender:AnyObject?) {
    let vc = ExtraViewController(nibName: "ExtraViewController", bundle: nil)
    self.presentViewController(vc, animated: true, completion: nil)
}

Run the project and tap the button. Observe that the presented view controller’s view occupies the entire interface, covering even the tab bar; it replaces the root view, because the presentation style is .FullScreen. The presenting view controller is the root view controller, which is the UITabBarController.

Now change the code to look like this:

@IBAction func doPresent(sender:AnyObject?) {
    let vc = ExtraViewController(nibName: "ExtraViewController", bundle: nil)
    self.definesPresentationContext = true // *
    vc.modalPresentationStyle = .CurrentContext // *
    self.presentViewController(vc, animated: true, completion: nil)
}

Run the project and tap the button. The presented view controller’s view replaces only the first view controller’s view; the tab bar remains, and you can switch back and forth between the tab bar’s first and second views even while the first view remains covered by the presented view. That’s because the presented view controller’s modalPresentationStyle is .CurrentContext, and definesPresentationContext is true in FirstViewController, which is the original presenter. Thus the search for a context stops in FirstViewController, which thus becomes the presenting view controller — meaning that the presented view replaces FirstViewController’s view instead of the root view.

We can also override the presented view controller’s transition animation through the modalTransitionStyle property of the presenting view controller:

@IBAction func doPresent(sender:AnyObject?) {
    let vc = ExtraViewController(nibName: "ExtraViewController", bundle: nil)
    self.definesPresentationContext = true
    self.providesPresentationContextTransitionStyle = true // *
    self.modalTransitionStyle = .CoverVertical // *
    vc.modalPresentationStyle = .CurrentContext
    vc.modalTransitionStyle = .FlipHorizontal // this will be overridden
    self.presentViewController(vc, animated: true, completion: nil)
}

Because the presenting view controller’s providesPresentationContextTransitionStyle is true, the transition uses the .CoverVertical animation belonging to the presenting view controller, rather than the .FlipHorizontal animation of the presented view controller.

When a view controller is presented, if its presentation style is not .FullScreen, a question arises of whether its status bar methods (prefersStatusBarHidden and preferredStatusBarStyle) should be consulted. By default, the answer is no, because this view controller is not the top-level view controller. To make the answer be yes, set this view controller’s modalPresentationCapturesStatusBarAppearance to true.

Adaptive Presentation

When a view controller with a modalPresentationStyle of .PageSheet or .FormSheet is about to appear, you get a second opportunity to change its effective modalPresentationStyle, and even to substitute a different view controller, based on the current trait collection environment. This is called adaptive presentation. The idea is that your presented view controller might appear one way for certain trait collections and another way for others — for example, on an iPad as opposed to an iPhone.

To implement adaptive presentation, you use a view controller’s presentation controller (presentationController, a UIPresentationController). Before presenting a view controller, you set its presentation controller’s delegate (adopting the UIAdaptivePresentationControllerDelegate protocol). Before the presented view controller’s view appears, the delegate is sent these messages:

adaptivePresentationStyleForPresentationController:traitCollection:

Asks for a modal presentation style. The first parameter is the presentation controller; you can consult its presentationStyle to learn what the modalPresentationStyle would be. Return .None if you don’t want to change the presentation style.

(This method, introduced in iOS 8.3, supersedes the simpler adaptivePresentationStyleForPresentationController:, which doesn’t include the traitCollection: information, and which isn’t called under all circumstances.)

presentationController:willPresentWithAdaptiveStyle:transitionCoordinator:
Called just before the presentation takes place. If the adaptiveStyle: is .None, adaptive presention is not going to take place.
presentationController:viewControllerForAdaptivePresentationStyle:
Called only if adaptive presention is going to take place. Return a view controller to be presented, substituting it for the current presented view controller.

What adaptations are you permitted to perform? First, as I’ve already said, the original modalPresentationStyle should be .PageSheet or .FormSheet. It isn’t illegal to try to adapt from other presentation styles, but it isn’t going to work either. Then the possibilities are as follows:

Adapt sheet to full screen

You can adapt .PageSheet or .FormSheet to .FullScreen or .OverFullScreen.

If the trait collection’s horizontal size class is .Compact (an iPhone, including an iPhone 6 Plus in portrait), then if you adapt .PageSheet or .FormSheet to .PageSheet, .CurrentContext, .OverCurrentContext, or even .Popover, the runtime behaves as if you had adapted to .FullScreen.)

Adapt page sheet to form sheet

You can adapt .PageSheet to .FormSheet.

On an iPad, the difference is clearly visible; on an iPhone 6 Plus in landscape, the two sheet types are indistinguishable.

On an iPhone (including an iPhone 6 Plus in portrait), the result is something a little unusual and unexpected. It’s similar to .PageSheet on the iPad in portrait orientation: it is full width, but a little shorter than full height, leaving a gap behind the status bar. (You can also obtain this configuration by adapting .PageSheet or .FormSheet to .None.)

For example, here’s how to present a view controller as a .PageSheet on iPad but as .OverFullScreen on iPhone:

extension ViewController : UIAdaptivePresentationControllerDelegate {
    @IBAction func doPresent(sender:AnyObject?) {
        let svc = SecondViewController(
            nibName: "SecondViewController", bundle: nil)
        svc.modalPresentationStyle = .FormSheet
        svc.presentationController!.delegate = self // *
        self.presentViewController(svc, animated:true, completion:nil)
    }
    func adaptivePresentationStyleForPresentationController(
        controller: UIPresentationController,
        traitCollection: UITraitCollection) -> UIModalPresentationStyle {
            if traitCollection.horizontalSizeClass == .Compact {
                return .OverFullScreen
            }
            return .None // don't adapt
    }
}

Now let’s extend that example by also replacing the view controller presented on iPad with a different view controller to be presented on iPhone:

extension ViewController : UIAdaptivePresentationControllerDelegate {
    // ... same as before ...
    func presentationController(controller: UIPresentationController,
        viewControllerForAdaptivePresentationStyle
        style: UIModalPresentationStyle) -> UIViewController? {
            let newvc = ThirdViewController(
                nibName: "ThirdViewController", bundle: nil)
            return newvc
    }
}

In real life, of course, when substituting a different view controller, you might need to prepare it before returning it (for example, giving it data and setting its delegate). A common scenario is to return the same view controller wrapped in a navigation controller; I’ll illustrate in Chapter 9.

Rotation of a Presented View

When the presenting view controller is the top-level view controller — the root view controller, or a fullscreen presented view controller — the presented view controller becomes the new top-level view controller. This means that its supportedInterfaceOrientations is consulted and honored. If these supportedInterfaceOrientations do not intersect with the app’s current orientation, the app’s orientation will rotate, as the presented view appears, to an orientation that the presented view controller supports — and the same thing will be true in reverse when the presented view controller is dismissed.

Thus, a presented view controller allows you to force the interface to rotate. In fact, a presented view controller is the only officially sanctioned way to force the interface to rotate.

Forced rotation is a perfectly reasonable thing to do, especially on the iPhone, where the user can easily rotate the device to compensate for the new orientation of the interface. Some views work better in portrait than landscape, or better in landscape than portrait (especially on the small screen). Forced rotation lets you ensure that each view appears only in the orientation in which it works best.

The presented view controller’s supportedInterfaceOrientations bitmask might permit multiple possible orientations. The view controller may then also wish to specify which of those multiple orientations it should have initially when it is presented. To do so, override preferredInterfaceOrientationForPresentation; this method is called before supportedInterfaceOrientations, and should return a single UIInterfaceOrientation (not a bitmask). For example:

override func preferredInterfaceOrientationForPresentation()
    -> UIInterfaceOrientation {
        return .LandscapeLeft
}

Tab Bar Controller

A tab bar (UITabBar, see also Chapter 12) is a horizontal bar containing items. Each item is a UITabBarItem; it displays, by default, an image and a title. At all times, exactly one of these items is selected (highlighted); when the user taps an item, it becomes the selected item.

If there are too many items to fit on a tab bar, the excess items are automatically subsumed into a final More item. When the user taps the More item, a list of the excess items appears, and the user can select one; the user can also be permitted to edit the tab bar, determining which items appear in the tab bar itself and which ones spill over into the More list.

A tab bar is an independent interface object, but it is most commonly used in conjunction with a tab bar controller (UITabBarController, a subclass of UIViewController) to form a tab bar interface. The tab bar controller displays the tab bar at the bottom of its own view. From the user’s standpoint, the tab bar items correspond to views; when the user selects a tab bar item, the corresponding view appears. The user is thus employing the tab bar to choose an entire area of your app’s functionality. In reality, the UITabBarController is a parent view controller; you give it child view controllers, which the tab bar controller then contains, and the views summoned by tapping the tab bar items are the views of those child view controllers.

Familiar examples of a tab bar interface on the iPhone are Apple’s Clock app and Music app.

You can get a reference to the tab bar controller’s tab bar through its tabBar property. In general, you won’t need this. When using a tab bar interface by way of a UITabBarController, you do not interact (as a programmer) with the tab bar itself; you don’t create it or set its delegate. You provide the UITabBarController with children, and it does the rest; when the UITabBarController’s view is displayed, there’s the tab bar along with the view of the selected item. You can, however, customize the look of the tab bar (see Chapter 12 for details).

If a tab bar controller is the top-level view controller, it determines your app’s compensatory rotation behavior. To take a hand in that determination without having to subclass UITabBarController, make one of your objects the tab bar controller’s delegate (UITabBarControllerDelegate) and implement these methods, as needed:

  • tabBarControllerSupportedInterfaceOrientations:
  • tabBarControllerPreferredInterfaceOrientationForPresentation:

A top-level tab bar controller also determines your app’s status bar appearance. However, a tab bar controller implements childViewControllerForStatusBarStyle and childViewControllerForStatusBarHidden so that the actual decision is relegated to the child view controller whose view is currently being displayed. Thus, your preferredStatusBarStyle and prefersStatusBarHidden are consulted and obeyed.

Tab Bar Items

For each view controller you assign as a tab bar controller’s child, you’re going to need a tab bar item, which will appear as its representative in the tab bar. This tab bar item will be your child view controller’s tabBarItem. A tab bar item is a UITabBarItem; this is a subclass of UIBarItem, an abstract class that provides some of its most important properties, such as title, image, and enabled.

There are two ways to make a tab bar item:

By borrowing it from the system
Instantiate UITabBarItem using init(tabBarSystemItem:tag:), and assign the instance to your child view controller’s tabBarItem. Consult the documentation for the list of available system items. Unfortunately, you can’t customize a system tab bar item’s title; you must accept the title the system hands you. (You can’t work around this restriction by somehow copying a system tab bar item’s image.)
By making your own

Instantiate UITabBarItem using init(title:image:tag:) and assign the instance to your child view controller’s tabBarItem. Alternatively, use the view controller’s existing tabBarItem and set its image and title. Instead of setting the title of the tabBarItem, you can set the title property of the view controller itself; doing this automatically sets the title of its current tabBarItem (unless the tab bar item is a system tab bar item), though the converse is not true.

You can add a separate selectedImage later, or possibly by initializing with init(title:image:selectedImage:). The selectedImage will be displayed in place of the normal image when this tab bar item is selected in the tab bar.

The image (and selectedImage) for a tab bar item should be a 30×30 PNG; if it is larger, it will be scaled down as needed. By default, it will be treated as a transparency mask (a template): the hue of its pixels will be ignored, and the transparency of its pixels will be combined with the tab bar’s tintColor, which may be inherited from higher up the view hierarchy. However, you can instead display the image as is, and not as a transparency mask, by deriving an image whose rendering mode is .AlwaysOriginal (see Chapter 2).

You can also give a tab bar item a badge (see the documentation on the badgeValue property). Other ways in which you can customize the look of a tab bar item are discussed in Chapter 12. For example, you can control the font and style of the title, or you can give it an empty title and offset the image.

Configuring a Tab Bar Controller

Basic configuration of a tab bar controller is very simple: just hand it the view controllers that will be its children. To do so, collect those view controllers into an array and set the UITabBarController’s viewControllers property to that array. The view controllers in the array are now the tab bar controller’s child view controllers; the tab bar controller is the parentViewController of the view controllers in the array. The tab bar controller is also the tabBarController of the view controllers in the array and of all their children; thus a child view controller at any depth can learn that it is contained by a tab bar controller and can get a reference to that tab bar controller. The tab bar controller retains the array, and the array retains the child view controllers.

Here’s a simple example from one of my apps, in which I construct and display a tab bar interface in code:

var tabBarController = UITabBarController()
func application(application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [NSObject:AnyObject]?)
    -> Bool {
        self.window = UIWindow()
        let viewController1 = GameBoardController()
        let viewController2 = UINavigationController(
            rootViewController:SettingsController())
        self.tabBarController.viewControllers =
            [viewController1, viewController2]
        self.tabBarController.selectedIndex = 0
        self.tabBarController.delegate = self
        self.window!.rootViewController = self.tabBarController
        self.window!.makeKeyAndVisible()
        return true
}

The tab bar controller’s tab bar will automatically display the tabBarItem of each child view controller. The order of the tab bar items is the order of the view controllers in the tab bar controller’s viewControllers array. Thus, a child view controller will probably want to configure its tabBarItem property early in its lifetime, so that the tabBarItem is ready by the time the view controller is handed as a child to the tab bar controller. Observe that viewDidLoad is not early enough! That’s because the view controllers (other than the initially selected view controller) have no view when the tab bar controller initially appears. Thus it is common to implement an initializer for this purpose.

Here’s an example from the same app as the previous code (in the GameBoardController class):

init() {
    super.init(nibName:nil, bundle:nil)
    // tab bar configuration
    self.tabBarItem.image = UIImage(named: "game.png")
    self.title = "Game"
}

In the tab bar, the image "game.png" is displayed as is, not as a template. I don’t have to call imageWithRenderingMode: because the rendering mode is set directly in the asset catalog that holds the image.

If you change the tab bar controller’s view controllers array later in its lifetime and you want the corresponding change in the tab bar’s display of its items to be animated, call setViewControllers:animated:.

Initially, by default, the first child view controller’s tab bar item is selected and its view is displayed. To ask the tab bar controller which tab bar item the user has selected, you can couch your query in terms of the child view controller (selectedViewController) or by index number in the array (selectedIndex). You can also set these properties to switch between displayed child view controllers programmatically. (In fact, it is legal to set a tab bar controller’s tab bar’s hidden to true and take charge of switching between children yourself — though if that’s your desired architecture, a UIPageViewController without animation or gesture response might be more appropriate.)

Note

You can supply an animation when a tab bar controller’s selected tab item changes and one child view controller’s view is replaced by another. I’ll discuss that topic later in the chapter.

You can also set the UITabBarController’s delegate (adopting UITabBarControllerDelegate). The delegate gets messages allowing it to prevent a given tab bar item from being selected, and notifying it when a tab bar item is selected and when the user is customizing the tab bar from the More item.

If the tab bar contains few enough items that it doesn’t need a More item, there won’t be one, and the tab bar won’t be user-customizable. If there is a More item, you can exclude some tab bar items from being customizable by setting the customizableViewControllers property to an array that lacks them; setting this property to nil means that the user can see the More list but can’t rearrange the items. Setting the viewControllers property sets the customizableViewControllers property to the same value, so if you’re going to set the customizableViewControllers property, do it after setting the viewControllers property. The moreNavigationController property can be compared with the selectedViewController property to learn whether the user is currently viewing the More list; apart from this, the More interface is mostly out of your control, but I’ll discuss some sneaky ways of customizing it in Chapter 12.

(If you allow the user to rearrange items, you would presumably want to save the new arrangement and restore it the next time the app runs. You might use NSUserDefaults for this; you could also take advantage of the built-in automatic state saving and restoration facilities, discussed later in this chapter.)

You can configure a UITabBarController in a .storyboard or .xib file. The UITabBarController’s contained view controllers can be set directly — in a storyboard, there will be a “view controllers” relationship between the tab bar controller and each of its children — and the contained view controllers will be instantiated together with the tab bar controller. Moreover, each contained view controller has a Tab Bar Item; you can select this and set many aspects of the tabBarItem, such as its system item or its title, image, selected image, and tag, directly in the nib. (If a view controller in a nib doesn’t have a Tab Bar Item and you want to configure this view controller for use in a tab bar interface, drag a Tab Bar Item from the Object library onto the view controller.)

To start a project with a main storyboard that has a UITabBarController as its root view controller, begin with the Tabbed Application template.

Navigation Controller

A navigation bar (UINavigationBar, see also Chapter 12) is a horizontal bar displaying a center title and a right button. When the user taps the right button, the navigation bar animates, sliding its interface out to the left and replacing it with a new interface that enters from the right. The new interface displays a back button at the left side, and a new center title — and possibly a new right button. The user can tap the back button to go back to the first interface, which slides in from the left; or, if there’s a right button in the second interface, the user can tap it to go further forward to a third interface, which slides in from the right.

The successive interfaces of a navigation bar thus behave like a stack. In fact, a navigation bar does represent an actual stack — an internal stack of navigation items (UINavigationItem). It starts out with one navigation item: the root or bottom item of the stack. Since there is just one navigation item, this is also the top item of the stack (the navigation bar’s topItem). It is the top item whose interface is always reflected in the navigation bar. When the user taps a right button, a new navigation item is pushed onto the stack; it becomes the top item, and its interface is seen. When the user taps a back button, the top item is popped off the stack, and what was previously the next item beneath it in the stack — the back item (the navigation bar’s backItem) — becomes the top item, and its interface is seen.

The state of the stack is thus reflected in the navigation bar’s interface. The navigation bar’s center title comes automatically from the top item, and its back button comes from the back item. (See Chapter 12 for a complete description.) Thus, the title tells the user what item is current, and the left side is a button telling the user what item we would return to if the user were to tap that button. The animations reinforce this notion of directionality, giving the user a sense of position within a chain of items.

A navigation bar is an independent interface object, but it is most commonly used in conjunction with a navigation controller (UINavigationController, a subclass of UIViewController) to form a navigation interface. Just as there is a stack of navigation items in the navigation bar, there is a stack of view controllers in the navigation controller. These view controllers are the navigation controller’s children, and each navigation item belongs to a view controller — it is a view controller’s navigationItem.

The navigation controller performs automatic coordination of the navigation bar and the overall interface. Whenever a view controller comes to the top of the navigation controller’s stack, its view is displayed in the interface. At the same time, its navigationItem is automatically pushed onto the top of the navigation bar’s stack — and is thus displayed in the navigation bar. Moreover, the animation in the navigation bar is reinforced by animation of the interface as a whole: by default, a view controller’s view slides into the main interface from the left or right just as its navigation item slides into the navigation bar from the left or right.

Note

You can supply a different animation when a view controller is pushed onto or popped off of a navigation controller’s stack. I’ll discuss this topic later in the chapter.

Your code can control the overall navigation, so in real life, the user may well navigate to the right, not by tapping the right button in the navigation bar, but by tapping something inside the main interface, such as a listing in a table view. (Figure 6-1 is a navigation interface that works this way.) In this situation, your code is deciding in real time what the next view should be; typically, you won’t even create the next view controller until the user asks to navigate to it. The navigation interface thus becomes a master–detail interface.

Conversely, you might put a view controller inside a navigation controller just to get the convenience of the navigation bar, with its title and buttons, even when no actual push-and-pop navigation is going to take place.

You can get a reference to the navigation controller’s navigation bar through its navigationBar property. In general, you won’t need this. When using a navigation interface by way of a UINavigationController, you do not interact (as a programmer) with the navigation bar itself; you don’t create it or set its delegate. You provide the UINavigationController with children, and it does the rest, handing each child view controller’s navigationItem to the navigation bar for display and showing the child view controller’s view each time navigation occurs. You can, however, customize the look of the navigation bar (see Chapter 12 for details).

A navigation interface may also optionally display a toolbar at the bottom. A toolbar (UIToolbar) is a horizontal view displaying a row of items, any of which the user can tap. Typically, the tapped item may highlight momentarily, but it is not selected; it represents the initiation of an action, like a button. You can get a reference to a UINavigationController’s toolbar through its toolbar property. The look of the toolbar can be customized (Chapter 12). In a navigation interface, however, the contents of the toolbar are determined automatically by the view controller that is currently the top item in the stack: they are its toolbarItems.

Note

A UIToolbar can also be used independently, and often is. It then typically appears at the bottom on an iPhone — Figure 6-3 has a toolbar at the bottom — but often appears at the top on an iPad, where it plays something of the role that the menu bar plays on the desktop. When a toolbar is displayed by a navigation controller, though, it always appears at the bottom.

A familiar example of a navigation interface is Apple’s Settings app on the iPhone. The Mail app on the iPhone is a navigation interface that includes a toolbar.

If a navigation controller is the top-level view controller, it determines your app’s compensatory rotation behavior. To take a hand in that determination without having to subclass UINavigationController, make one of your objects the navigation controller’s delegate (UINavigationControllerDelegate) and implement these methods, as needed:

  • navigationControllerSupportedInterfaceOrientations:
  • navigationControllerPreferredInterfaceOrientationForPresentation:

A top-level navigation controller also determines your app’s status bar appearance. However, a navigation controller implements childViewControllerForStatusBarHidden so that the actual decision is relegated to the child view controller whose view is currently being displayed.

Your child view controllers can implement preferredStatusBarStyle, and the navigation controller’s childViewControllerForStatusBarStyle defers to its top child view controller — but only if the navigation bar is hidden. If the navigation bar is showing, the navigation controller sets the status bar style based on the navigation bar’s barStyle — to .Default if the bar style is .Default, and to .LightContent if the bar style is .Black. So, the way to change the status bar style is to change the navigation bar style. Alternatively, you can subclass UINavigationController and compel the navigation controller to respect the top child view controller’s preferredStatusBarStyle, like this:

override func childViewControllerForStatusBarStyle() -> UIViewController? {
    let vc = super.childViewControllerForStatusBarStyle()
    return self.topViewController
}

Bar Button Items

The buttons in a UIToolbar or a UINavigationBar are bar button items — UIBarButtonItem, a subclass of UIBarItem. A bar button item comes in one of two broadly different flavors:

Basic bar button item
The bar button item behaves like a simple button.
Custom view
The bar button item has no inherent behavior, but has (and displays) a customView.

UIBarItem is not a UIView subclass. A basic bar button item is button-like, but it has no frame, no UIView touch handling, and so forth. A UIBarButtonItem’s customView, however, is a UIView — indeed, it can be any kind of UIView. Thus, a bar button item with a customView can display any sort of view in a toolbar or navigation bar, and that view can implement touch handling however it likes.

Let’s start with the basic bar button item (no custom view). A bar button item, like a tab bar item, inherits from UIBarItem the title, image, and enabled properties. The title text color, by default, comes from the bar button item’s tintColor, which may be inherited from the bar itself or from higher up the view hierarchy. Assigning an image removes the title. The image should usually be quite small; Apple recommends 22×22. By default, it will be treated as a transparency mask (a template): the hue of its pixels will be ignored, and the transparency of its pixels will be combined with the bar button item’s tintColor. However, you can instead display the image as is, and not as a transparency mask, by deriving an image whose rendering mode is .AlwaysOriginal (see Chapter 2).

A basic bar button item has a style property (UIBarButtonItemStyle); this will usually be .Plain. The alternative, .Done, causes the title to be bold. You can further refine the title font and style. In addition, a bar button item can have a background image; this will typically be a small, resizable image, and can be used to provide a border. Full details appear in Chapter 12.

A bar button item also has target and action properties. These contribute to its button-like behavior: tapping a bar button item can trigger an action method elsewhere.

There are three ways to make a bar button item:

By borrowing it from the system
Make a UIBarButtonItem with init(barButtonSystemItem:target:​action:). Consult the documentation for the list of available system items; they are not the same as for a tab bar item. You can’t assign a title or change the image. (But you can change the tint color or assign a background image.)
By making your own basic bar button item

Make a UIBarButtonItem with init(title:style:target:action:) or with init(image:style:target:action:).

An additional initializer, init(image:landscapeImagePhone:style:target:​action:), lets you supply two images, one for portrait orientation, the other for landscape orientation; this is because by default, the bar’s height might change when the interface is rotated. Alternatively, you can use size class–aware images to handle this situation (see Chapter 2).

By making a custom view bar button item
Make a UIBarButtonItem with init(customView:), supplying a UIView that the bar button item is to display. The bar button item has no action and target; the UIView itself must somehow implement button behavior if that’s what you want. For example, the customView might be a UISegmentedButton, but then it is the UISegmentedButton’s target and action that give it button behavior.

Bar button items in a toolbar are horizontally positioned automatically by the system. You can provide hints to help with this positioning. If you know that you’ll be changing an item’s title dynamically, you’ll probably want its width to accommodate the longest possible title right from the start; to arrange that, set the possibleTitles property to a Set of strings that includes the longest title. Alternatively, you can supply an absolute width. Also, you can incorporate spacers into the toolbar; these are created with init(barButtonSystemItem:target:action:), but they have no visible appearance, and cannot be tapped. Place .FlexibleSpace system items between the visible items to distribute the visible items equally across the width of the toolbar. There is also a .FixedSpace system item whose width lets you insert a space of defined size.

Navigation Items and Toolbar Items

What appears in a navigation bar (UINavigationBar) depends upon the navigation items (UINavigationItem) in its stack. In a navigation interface, the navigation controller will manage the navigation bar’s stack for you, but you must still configure each navigation item by setting properties of the navigationItem of each child view controller. The UINavigationItem properties are as follows (see also Chapter 12):

title or titleView

Determines what is to appear in the center of the navigation bar when this navigation item is at the top of the stack.

The title is a string. Setting the view controller’s title property sets the title of the navigationItem automatically, and is usually the best approach.

The titleView can be any kind of UIView; if set, it will be displayed instead of the title. The titleView can implement further UIView functionality; for example, it can be tappable. Even if you are using a titleView, you should still give your view controller a title, as it will be needed for the back button when a view controller is pushed onto the stack on top of this one.

Figure 6-1 shows the TidBITS News master view, with the navigation bar displaying a titleView which is a (tappable) image view; the master view controller’s title, which is "TidBITS", is therefore not displayed. In the TidBITS News detail view controller’s navigation item, the titleView is a segmented control providing a Previous and Next button; when it is pushed onto the stack, the back button displays the master view controller’s title (Figure 6-8).

A segmented control in the center of a navigation bar
Figure 6-8. A segmented control in the center of a navigation bar
prompt
An optional string to appear centered above everything else in the navigation bar. The navigation bar’s height will be increased to accommodate it.
rightBarButtonItem or rightBarButtonItems

A bar button item or, respectively, an array of bar button items to appear at the right side of the navigation bar; the first item in the array will be rightmost.

In Figure 6-8, the text size button is a right bar button item; it has nothing to do with navigation, but is placed here merely because space is at a premium on the small iPhone screen.

backBarButtonItem

When a view controller is pushed on top of this view controller, the navigation bar will display at its left a button pointing to the left, whose title is this view controller’s title. That button is this view controller’s navigation item’s backBarButtonItem. That’s right: the back button displayed in the navigation bar belongs, not to the top item (the navigationItem of the current view controller), but to the back item (the navigationItem of the view controller that is one level down in the stack). In Figure 6-8, the back button in the detail view is the master view controller’s default back button, displaying its title.

The vast majority of the time, the default behavior is the behavior you’ll want, and you’ll leave the back button alone. If you wish, though, you can customize the back button by setting a view controller’s navigationItem.backBarButtonItem so that it contains an image, or a title differing from the view controller’s title. The best technique is to provide a new UIBarButtonItem whose target and action are nil; the runtime will add a correct target and action, so as to create a working back button. Here’s how to create a back button with a custom image instead of a title:

let b = UIBarButtonItem(
    image:UIImage(named:"files.png"), style:.Plain, target:nil, action:nil)
self.navigationItem.backBarButtonItem = b

A Bool property, hidesBackButton, allows the top navigation item to suppress display of the back item’s back bar button item. If you set this to true, you’ll probably want to provide some other means of letting the user navigate back.

The visible indication that the back button is a back button is a left-pointing chevron (the back indicator) that’s separate from the button itself. This chevron can also be customized, but it’s a feature of the navigation bar, not the bar button item: set the navigation bar’s backIndicatorImage and backIndicatorTransitionMask. (I’ll give an example in Chapter 12.) Alternatively, if the back button is assigned a background image, the back indicator is removed; it is up to the background image to point left, if desired.

leftBarButtonItem or leftBarButtonItems
A bar button item or, respectively, an array of bar button items to appear at the left side of the navigation bar; the first item in the array will be leftmost. The leftItemsSupplementBackButton property, if set to true, allows both the back button and one or more left bar button items to appear.

A view controller’s navigation item can have its properties set at any time while being displayed in the navigation bar. This (and not direct manipulation of the navigation bar) is the way to change the navigation bar’s contents dynamically. For example, in one of my apps, the titleView is a progress view (UIProgressView, Chapter 12) that needs updating every second, and the right bar button should be either the system Play button or the system Pause button, depending on whether music from the library is playing, paused, or stopped. So I have a timer that periodically checks the state of the music player; observe how we access the progress view and the right bar button by way of self.navigationItem:

// change the progress view
let prog = self.navigationItem.titleView as! UIProgressView
if let item = self.nowPlayingItem {
    let current = self.mp.currentPlaybackTime
    let total = item.valueForProperty(
        MPMediaItemPropertyPlaybackDuration) as! Double
    prog.progress = Float(current / total)
} else {
    prog.progress = 0
}
// change the bar button
let whichButton : UIBarButtonSystemItem?
switch self.mp.currentPlaybackRate {
case 0..<0.1:
    whichButton = .Play
case 0.1...1.0:
    whichButton = .Pause
default:
    whichButton = nil
}
if let which = whichButton {
    let bb = UIBarButtonItem(barButtonSystemItem: which,
        target: self, action: "doPlayPause:")
    self.navigationItem.rightBarButtonItem = bb
}

Each view controller to be pushed onto the navigation controller’s stack is responsible also for supplying the items to appear in the navigation interface’s toolbar, if there is one. To configure this, set the view controller’s toolbarItems property to an array of UIBarButtonItem instances. You can change the toolbar items even while the view controller’s view and current toolbarItems are showing, optionally with animation, by sending setToolbarItems:animated: to the view controller.

Configuring a Navigation Controller

You configure a navigation controller by manipulating its stack of view controllers. This stack is the navigation controller’s viewControllers array property, though you will rarely need to manipulate that property directly.

The view controllers in a navigation controller’s viewControllers array are the navigation controller’s child view controllers; the navigation controller is the parentViewController of the view controllers in the array. The navigation controller is also the navigationController of the view controllers in the array and of all their children; thus a child view controller at any depth can learn that it is contained by a navigation controller and can get a reference to that navigation controller. The navigation controller retains the array, and the array retains the child view controllers.

The normal way to manipulate a navigation controller’s stack is by pushing or popping one view controller at a time. When the navigation controller is instantiated, it is usually initialized with init(rootViewController:); this is a convenience method that assigns the navigation controller a single initial child view controller, the root view controller that goes at the bottom of the stack:

let fvc = FirstViewController()
let nav = UINavigationController(rootViewController:fvc)

Instead of init(rootViewController:), you might choose to create the navigation controller with init(navigationBarClass:toolbarClass:), in order to set a custom subclass of UINavigationBar or UIToolbar. Typically, this will be in order to customize the appearance of the navigation bar and toolbar; sometimes you’ll create, say, a UIToolbar subclass for no other reason than to mark this kind of toolbar as needing a certain appearance. I’ll explain about that in Chapter 12. If you use this initializer, you’ll have to set the navigation controller’s root view controller separately.

You can also set the UINavigationController’s delegate (adopting UINavigationControllerDelegate). The delegate receives messages before and after a child view controller’s view is shown.

A navigation controller will typically appear on the screen initially containing just its root view controller, and displaying its root view controller’s view. There will be no back button, because there is no back item; there is nowhere to go back to. Subsequently, when the user asks to navigate to a new view, you (typically meaning code in the current view controller) obtain the next view controller (typically by creating it) and push it onto the stack by calling pushViewController:animated: on the navigation controller. The navigation controller performs the animation, and displays the new view controller’s view:

let svc = SecondViewController()
self.navigationController!.pushViewController(svc, animated: true)

There is usually no need to worry about going back; when the user taps the back button to navigate back, the runtime will call popViewControllerAnimated: for you. When a view controller is popped from the stack, the viewControllers array removes and releases the view controller, which is usually permitted to go out of existence at that point.

Alternatively, there’s a second way to push a view controller onto the navigation controller’s stack, without referring to the navigation controller: showViewController:sender:. This method lets the caller be agnostic about the current interface situation; for example, it pushes onto a navigation controller if the view controller to which it is sent is in a navigation interface, but generates a presented view controller if not. I’ll talk more about this method in Chapter 9; meanwhile, I’ll continue using pushViewController:animated: in my examples.

Instead of tapping the back button, the user can go back by dragging a pushed view controller’s view from the left edge of the screen. This is actually a way of calling popViewControllerAnimated:, with the difference that the animation is interactive. (Interactive view controller transition animation is the subject of the next section.) The UINavigationController uses a UIScreenEdgePanGestureRecognizer to detect and track the user’s gesture. You can obtain a reference to this gesture recognizer as the navigation controller’s interactivePopGestureRecognizer; thus you can disable the gesture recognizer and prevent this way of going back, or you can mediate between your own gesture recognizers and this one (see Chapter 5).

You can manipulate the stack more directly if you wish. You can call popViewControllerAnimated: explicitly; to pop multiple items so as to leave a particular view controller at the top of the stack, call popToViewController:animated:, or to pop all the items down to the root view controller, call popToRootViewControllerAnimated:. All of these methods return the popped view controller (or view controllers, as an array), in case you want to do something with them.

To set the entire stack at once, call setViewControllers:animated:. You can access the stack through the viewControllers property. Manipulating the stack directly is the only way, for instance, to delete or insert a view controller in the middle of the stack.

The view controller at the top of the stack is the topViewController; the view controller whose view is displayed is the visibleViewController. Those will normally be the same, but they needn’t be, as the topViewController might present a view controller, in which case the presented view controller will be the visibleViewController. Other view controllers can be accessed through the viewControllers array by index number. The root view controller is at index 0; if the array’s count is c, the back view controller (the one whose navigationItem.backBarButtonItem is currently displayed in the navigation bar) is at index c-2.

The topViewController may need to communicate with the next view controller as the latter is pushed onto the stack, or with the back view controller as it itself is popped off the stack. The problem is parallel to that of communication between an original presenter and a presented view controller, which I discussed earlier in this chapter (Communication With a Presented View Controller), so I won’t say more about it here.

A child view controller will probably want to configure its navigationItem early in its lifetime, so as to be ready for display in the navigation bar by the time the view controller is handed as a child to the navigation controller. Apple warns (in the UIViewController class reference, under navigationItem) that loadView and viewDidLoad are not appropriate places to do this, because the circumstances under which the view is needed are not related to the circumstances under which the navigation item is needed. Apple’s own code examples routinely violate this warning, but it is probably best to override init(nibName:bundle:) — or init(coder:) or awakeFromNib, if appropriate — for this purpose.

A navigation controller’s navigation bar is accessible as its navigationBar, and can be hidden and shown with setNavigationBarHidden:animated:. (It is possible, though not common, to maintain and manipulate a navigation stack through a navigation controller whose navigation bar never appears.) Its toolbar is accessible as its toolbar, and can be hidden and shown with setToolbarHidden:animated:.

A view controller also has the power to specify that its ancestor’s bottom bar (a navigation controller’s toolbar, or a tab bar controller’s tab bar) should be hidden as this view controller is pushed onto a navigation controller’s stack. To do so, set the view controller’s hidesBottomBarWhenPushed property to true. The trick is that you must do this very early, before the view loads; the view controller’s initializer is a good place. The bottom bar remains hidden from the time this view controller is pushed to the time it is popped, even if other view controllers are pushed and popped on top of it in the meantime.

A navigation controller can perform automatic hiding and showing of its navigation bar (and, if normally shown, its toolbar) in response to various situations, as configured by properties:

When tapped
If the navigation controller’s hidesBarsOnTap is true, a tap that falls through the top view controller’s view is taken as a signal to toggle bar visibility. The relevant gesture recognizer is the navigation controller’s barHideOnTapGestureRecognizer.
When swiped
If the navigation controller’s hidesBarsOnSwipe is true, an upward or downward swipe respectively hides or shows the bars. The relevant gesture recognizer is the navigation controller’s barHideOnSwipeGestureRecognizer.
In landscape
If the navigation controller’s hidesBarsWhenVerticallyCompact is true, bars are automatically hidden when the app rotates to landscape on the iPhone (and hidesBarsOnTap is treated as true, so the bars can be shown again by tapping).
When the user is typing
If the navigation controller’s hidesBarsWhenKeyboardAppears is true, bars are automatically hidden when the onscreen keyboard appears (see Chapter 10).

You can configure a UINavigationController, or any view controller that is to serve in a navigation interface, in a .storyboard or .xib file. In the Attributes inspector, use a navigation controller’s Bar Visibility and Hide Bars checkboxes to determine the presence of the navigation bar and toolbar. The navigation bar and toolbar are themselves subviews of the navigation controller, and you can configure them with the Attributes inspector as well. A navigation controller’s root view controller can be specified; in a storyboard, there will be a “root view controller” relationship between the navigation controller and its root view controller.

A view controller in a .storyboard or .xib file has a Navigation Item where you can specify its title, its prompt, and the text of its back button. (If a view controller in a nib doesn’t have a Navigation Item and you want to configure this view controller for use in a navigation interface, drag a Navigation Item from the Object library onto the view controller.) You can drag Bar Button Items into a view controller’s navigation bar in the canvas to set the left buttons and right buttons of its navigationItem. Moreover, the Navigation Item has outlets, one of which permits you to set its titleView. Similarly, you can give a view controller Bar Button Items that will appear in the toolbar.

To start an iPhone project with a main storyboard that has a UINavigationController as its root view controller, begin with the Master–Detail Application template. Alternatively, start with the Single View Application template, remove the existing view controller from the storyboard, and add a Navigation Controller in its place. Unfortunately, the nib editor assumes that the navigation controller’s root view controller should be a UITableViewController. If that’s not the case, here’s a better way: start with the Single View Application template, select the existing view controller, and choose Editor → Embed In → Navigation Controller. A view controller to be subsequently pushed onto the navigation stack can be configured in the storyboard as the destination of a push segue; I’ll talk more about that later in the chapter.

Custom Transition

You can customize the transition that occurs between view controller views, as follows:

  • When a tab bar controller changes which of its child view controllers is selected, you can animate the change of views.
  • When a navigation controller pushes or pops a child view controller, you can customize the animation of views.
  • When a view controller is presented or dismissed, you can customize the animation of views and the placement of the presented view.

Given the extensive animation resources of iOS (see Chapter 4), this is an excellent chance for you to provide your app with variety, interest, and distinctiveness. The view of a child view controller pushed onto a navigation controller’s stack, for example, needn’t arrive sliding from the side; it can expand by zooming from the middle of the screen, drop from above and fall into place with a bounce, snap into place like a spring, or whatever else you can dream up. A familiar example is Apple’s Calendar app, which transitions from a year to a month, in a navigation controller, by zooming in.

A custom transition animation can optionally be interactive — meaning that it is driven in real time by the user’s gesture. The user does not merely tap and cause an animation to take place; the user performs an extended gesture and gradually summons the new view to supersede the old one. The user can thus participate in the progress of the transition. An example is the way a navigation controller’s view can be popped by dragging from the left edge of the screen. Another example is the Photos app, which lets the user pinch a photo, in a navigation controller, to pop to the album containing it.

In the case of a presented view controller, you also get to dictate the ultimate size and position of the presented view, and how the presenting view is seen behind it; you can also provide intermediate views that remain during the presentation. The presentation controller (introduced earlier in this chapter) participates in, and is crucial to, the transition.

I’ll start by talking about how to add a custom animation to a tab bar controller transition, and work up to the more involved business of customizing a view controller presentation.

Noninteractive Custom Transition Animation

Let’s start with the base case, where the custom animation is not interactive. Configuring your custom animation requires three steps:

  1. The view controller in charge of the transition must have a delegate.
  2. As the transition begins, the delegate will be asked for an animation controller, meaning any object adopting the UIViewControllerAnimatedTransitioning protocol. Return nil to specify that the default animation (if any) should be used.
  3. The animation controller will be sent two messages:

    transitionDuration:
    The animation controller must return the duration of the custom animation.
    animateTransition:
    The animation controller should perform the animation.

The implementation of animateTransition: works, in general, as follows:

  1. The parameter is an object called the transition context (adopting the UIViewControllerContextTransitioning protocol). By querying the transition context, you can obtain:

    • The container view, an already existing view within which all the action is to take place.
    • The outgoing and incoming view controllers.
    • The outgoing and incoming views. These are probably the main views of the outgoing and incoming view controllers, but you should obtain the views directly from the transition context, just in case they aren’t. The outgoing view is already inside the container view.
    • The initial frame of the outgoing view, and the ultimate frame of the incoming view.
  2. Having gathered this information, your mission is to put the incoming view into the container view and animate it in such a way as to end up at its correct ultimate position. You may also animate the outgoing view if you wish.
  3. When the animation ends, you must call the transition context’s completeTransition: to tell it that the animation is over. The outgoing view is then removed automatically.

To illustrate, consider the transition between two child view controllers of a tab bar controller. By default, this transition isn’t animated; one view just replaces the other. Let’s animate the transition.

One obvious custom animation is that the new view controller’s view should slide in from one side while the old view controller’s view should slide out the other side. The direction of the slide should depend on whether the index of the new view controller is greater or less than that of the old view controller.

Take the steps in order. First, configure a delegate for the tab bar controller. Assume that the tab bar controller is our app’s root view controller. For simplicity, I’ll set its delegate in code, in the app delegate’s application:didFinishLaunchingWithOptions:, and I’ll make that delegate be the app delegate itself:

(self.window!.rootViewController as! UITabBarController).delegate = self

On to the second step. The app delegate, in its role as UITabBarControllerDelegate, will now be sent a message whenever the tab bar controller is about to change view controllers. That message is:

  • tabBarController:animationControllerForTransitionFromViewController:toViewController:

We must implement this method to return an animation controller, namely, some object implementing UIViewControllerAnimatedTransitioning. In this case, to keep things simple, I’ll return self. Here we go:

func tabBarController(tabBarController: UITabBarController,
    animationControllerForTransitionFromViewController
    fromVC: UIViewController,
    toViewController toVC: UIViewController)
    -> UIViewControllerAnimatedTransitioning? {
        return self
}

(There is no particular reason why the animation controller should be self; it can be any object — even a dedicated lightweight object instantiated just to govern this transition. There is also no particular reason why the animation controller should be the same object every time this method is called. We know, in real time, what’s about to happen, because we receive the tab bar controller and both child view controllers as parameters. Thus we could readily provide a different animation controller under different circumstances, or we could return nil to use the default transition — meaning, in this case, no animation.)

On to the third step! Here we are in the animation controller (UIViewControllerAnimatedTransitioning). Our first job is to reveal in advance the duration of our animation:

func transitionDuration(
    transitionContext: UIViewControllerContextTransitioning?)
    -> NSTimeInterval {
        return 0.4
}

(Again, the value returned needn’t be the same every time this method is called. The transition context has arrived as parameter, and we could query it to identify the two view controllers involved and make a decision based on that. But make sure that the value you return here is indeed the duration of the animation you’ll perform in animateTransition:.)

Finally, we come to animateTransition: itself:

func animateTransition(
    transitionContext: UIViewControllerContextTransitioning) {
        // ...
}

Once more, simply perform the steps in order. First, query the transition context. This code is practically boilerplate for any custom view controller transition animation:

let vc1 = transitionContext.viewControllerForKey(
    UITransitionContextFromViewControllerKey)!
let vc2 = transitionContext.viewControllerForKey(
    UITransitionContextToViewControllerKey)!
let con = transitionContext.containerView()!
let r1start = transitionContext.initialFrameForViewController(vc1)
let r2end = transitionContext.finalFrameForViewController(vc2)
let v1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let v2 = transitionContext.viewForKey(UITransitionContextToViewKey)!

We have the view controllers and their views, and the initial frame of the outgoing view and the destination frame of the incoming view. Now, to prepare for our intended animation, we want to calculate the converse, namely the final frame of the outgoing view and the initial frame of the incoming view. We are sliding the views sideways, so those frames should be positioned sideways from the initial frame of the outgoing view and the final frame of the incoming view. Which side they go on depends upon the relative place of these view controllers among the children of the tab bar controller — is this to be a leftward slide or a rightward slide? Since the animation controller is the app delegate, we can get a reference to the tab bar controller the same way we did before:

let tbc = self.window!.rootViewController as! UITabBarController
let ix1 = tbc.viewControllers!.indexOf(vc1)!
let ix2 = tbc.viewControllers!.indexOf(vc2)!
let dir : CGFloat = ix1 < ix2 ? 1 : -1
var r1end = r1start
r1end.origin.x -= r1end.size.width * dir
var r2start = r2end
r2start.origin.x += r2start.size.width * dir

We are now ready for the second step: put the second view controller’s view into the container view at its initial frame, and perform the animation. The end of the animation is also the moment to perform the all-important third step, namely to call completeTransition: to signal that our part has been played and our hands are off the views:

v2.frame = r2start
con.addSubview(v2)
UIView.animateWithDuration(0.4, animations: {
    v1.frame = r1end
    v2.frame = r2end
    }, completion: {
        _ in
        transitionContext.completeTransition(true)
    })

That’s all there is to it. Of course, that wasn’t a very complex animation; but an animation needn’t be complex to be interesting, significant, and helpful to the user. And even a more complex animation would be implemented along the same basic lines. One possibility that I didn’t illustrate in my example is that you are free to introduce additional views temporarily into the container view during the course of the animation; you’ll probably want to remove them in the completion handler.

A custom transition for a navigation controller is similar to a custom transition for a tab bar controller, so I don’t need to give a separate example. The only slight difference lies in the name of the navigation controller delegate method that will be called to discover whether there’s an animation controller:

  • navigationController:animationControllerForOperation:fromViewController:toViewController:

The operation: parameter allows you to distinguish a push from a pop.

Warning

Implementing a navigation controller custom push animation while returning nil for the pop animation controller will cause the built-in swipe-to-pop interactive transition to stop working. I regard this as a bug.

Interactive Custom Transition Animation

With an interactive custom transition animation, the idea is that we track something the user is doing, typically by means of a gesture recognizer (see Chapter 5), and perform the “frames” of the transition in response. There are two ways to write an interactive custom transition animation. In both cases, we’re going to need an interaction controller, namely an object that conforms to the UIViewControllerInteractiveTransitioning protocol. The two ways of writing the code correspond to the two ways of supplying this object:

Create a UIPercentDrivenInteractiveTransition instance
We supply an instance of the built-in UIPercentDrivenInteractiveTransition class (let’s call this the percent driver). This percent driver object performs the frames of the animation for us by calling our animateTransition: and “freezing” the animation. All we have to do is track the gesture and repeatedly call the percent driver’s updateInteractiveTransition:, telling it how far the gesture has proceeded; the percent driver updates the interface, changing our animation’s “frame” to match the extent of the gesture. At the end of the gesture, we decide whether to finish or cancel the transition; accordingly, we call the percent driver’s finishInteractiveTransition or cancelInteractiveTransition. Finally, we call the transition context’s completeTransition:.
Adopt UIViewControllerInteractiveTransitioning
We supply our own object with our own code. This object, conforming to the UIViewControllerInteractiveTransitioning protocol, will need to respond to startInteractiveTransition:, whose parameter will be the transition context. Once we are told that the transition has started, we will set up the initial conditions for the animation and then constantly track the gesture, changing the interface and calling the transition context’s updateInteractiveTransition:. When the interaction ends, we decide whether to finish or cancel the transition; accordingly, we animate into the final or initial conditions, and call the transition context’s finishInteractiveTransition or cancelInteractiveTransition. Finally, we call the transition context’s completeTransition:.

Using a percent driver

If we have already written a noninteractive version of our transition animation, using the percent driver is going to be simplest, because we get to keep our existing animateTransition: code. The steps build upon those of a noninteractive transition animation:

  1. The view controller in charge of the transition must have a delegate.
  2. We observe that the user is gesturing in a way that should trigger a change of view controller. We respond by triggering that change.
  3. The delegate will be asked for an animation controller, as before. We return an object adopting the UIViewControllerAnimatedTransitioning protocol, as before.
  4. The delegate is also asked for an interaction controller. (This happened before, but we didn’t supply one, which is why our transition animation wasn’t interactive.) We have elected to use a UIPercentDrivenInteractiveTransition object. So we return that object.
  5. The animation controller is sent the same two messages as before:

    transitionDuration:
    The duration of the custom animation.
    animateTransition:

    The animation controller should perform the animation. But the animation will not in fact proceed at this moment; it will be “frozen” and its “frames” will be produced as the interaction proceeds.

    (To perform this magic, the percent driver takes advantage of the fact that a CALayer conforms to the CAMediaTiming protocol, as I explained in Chapter 4. It asks the transition context for the container view, obtains that view’s layer, and sets the layer’s speed to 0. Subsequently, as we call the percent driver’s updateInteractiveTransition:, it adjusts that layer’s timeOffset accordingly, thus displaying a different “frame” of the animation.)

  6. We continue tracking the interaction, calling our percent driver’s updateInteractiveTransition: to tell it how far the gesture has proceeded. The percent driver displays that frame of our animation for us.
  7. Sooner or later the gesture will end. At this point, we must decide whether to declare the transition completed or cancelled. The usual approach is to say that if the user performed more than half the full gesture, that constitutes completion; otherwise, it constitutes cancellation. We call the percent driver’s finishInteractiveTransition or cancelInteractiveTransition accordingly. The percent driver either completes the animation or (if we cancelled) reverses the animation.
  8. The animation is now completed, and its completion block is called. We call completeTransition:, with the argument stating whether the transition was finished or cancelled.

As an example, I’ll describe how to make an interactive version of the tab bar controller transition animation that we developed in the previous section, such that the user can drag the tab bar controller’s adjacent view controller in from the right or from the left. All the code we’ve already written can be left more or less as is! To track the user’s gesture, I’ll put a pair of UIScreenEdgePanGestureRecognizers into the interface, and keep references to them so they can be identified later. The gesture recognizers are attached to the tab bar controller’s view (tbc.view), as this will remain constant while the views of its view controllers are sliding across the screen:

let sep = UIScreenEdgePanGestureRecognizer(target:self, action:"pan:")
sep.edges = UIRectEdge.Right
tbc.view.addGestureRecognizer(sep)
sep.delegate = self
self.rightEdger = sep
let sep2 = UIScreenEdgePanGestureRecognizer(target:self, action:"pan:")
sep2.edges = UIRectEdge.Left
tbc.view.addGestureRecognizer(sep2)
sep2.delegate = self
self.leftEdger = sep2

Acting as the delegate of the two gesture recognizers, we prevent either pan gesture recognizer from operating unless there is another child of the tab bar controller available on that side of the current child:

func gestureRecognizerShouldBegin(g: UIGestureRecognizer) -> Bool {
    let tbc = self.window!.rootViewController as! UITabBarController
    var result = false
    if g == self.rightEdger {
        result = (tbc.selectedIndex < tbc.viewControllers!.count - 1)
    }
    else {
        result = (tbc.selectedIndex > 0)
    }
    return result
}

If the gesture recognizer action handler pan: is called, we now know that this means our interactive transition animation is to take place. I’ll break down the discussion according to the gesture recognizer’s stages. First, I collect some information that will be needed later:

func pan(g:UIScreenEdgePanGestureRecognizer) {
    let v = g.view!
    let tbc = self.window!.rootViewController as! UITabBarController
    let delta = g.translationInView(v)
    let percent = fabs(delta.x/v.bounds.size.width)
    switch g.state {
        // ... to be continued ...
    }
}

As the gesture begins, we create the UIPercentDrivenInteractiveTransition object and store it in a property (self.inter). We then set the tab bar controller’s selectedIndex:

case .Began:
    self.inter = UIPercentDrivenInteractiveTransition()
    self.interacting = true
    if g == self.rightEdger {
        tbc.selectedIndex = tbc.selectedIndex + 1
    } else {
        tbc.selectedIndex = tbc.selectedIndex - 1
    }

Changing the tab bar controller’s selectedIndex causes the runtime to turn to the tab bar controller’s delegate to see whether there is an animation controller, and hence a custom animation; as before, we make ourselves the animation controller:

func tabBarController(tabBarController: UITabBarController,
    animationControllerForTransitionFromViewController
    fromVC: UIViewController,
    toViewController toVC: UIViewController)
    -> UIViewControllerAnimatedTransitioning? {
        return self
}

The runtime now asks if there is also an interaction controller. There wasn’t one in our previous example, but now there is — the percent driver:

func tabBarController(tabBarController: UITabBarController,
    interactionControllerForAnimationController
    animationController: UIViewControllerAnimatedTransitioning)
    -> UIViewControllerInteractiveTransitioning? {
        let result : UIViewControllerInteractiveTransitioning? =
            self.interacting ? self.inter : nil
        return result
}

The runtime now calls our percent driver’s startInteractiveTransition:, handing it a reference to the transition context. The percent driver immediately turns around and calls our animateTransition: method, without performing the animation. The percent driver “freezes” the animation instead. Our job now is to keep calling the percent driver, telling it what “frame” of the animation to display at every moment; the percent driver will also call the transition context on our behalf.

We are now back in the gesture recognizer’s action handler. As the gesture proceeds, we keep sending updateInteractiveTransition: to the percent driver:

case .Changed:
    self.inter.updateInteractiveTransition(percent)

When the gesture ends, we decide whether this counts as finishing or canceling, and we report to the percent driver accordingly:

case .Ended:
    if percent > 0.5 {
        self.inter.finishInteractiveTransition()
    } else {
        self.inter.cancelInteractiveTransition()
    }
    self.interacting = false

If we call finishInteractiveTransition, the percent driver quickly plays the rest of the animation forward to completion. If we call cancelInteractiveTransition, the percent driver plays the animation backward to its beginning.

Finally, we find ourselves back inside animateTransition:, in the animation completion: handler. This is the only place where a change is needed in our previously existing code. As I’ve just said, the transition can complete in one of two ways. We must still call completeTransition: ourselves, but we must tell the transition context which way things turned out, so that the transition context can restore the previous state of things if the transition was cancelled. Luckily, the transition context already knows whether the transition was cancelled! So we simply ask it:

UIView.animateWithDuration(0.4, delay:0, options:opts, animations: {
    v1.frame = r1end
    v2.frame = r2end
    }, completion: {
        _ in
        let cancelled = transitionContext.transitionWasCancelled()
        transitionContext.completeTransition(!cancelled)
    })

That’s all there is to it, and in fact, as a bonus, I have sneakily added a bit of extra functionality to our code: not only does this work as an interactive transition, but I’ve also kept the noninteractive transition that we developed earlier. In other words, the user can tap a tab bar item to get the original sliding animation left or right, or the user can manually slide a view to get the interactive animation.

Without a percent driver

If we don’t use a percent driver, then the entire interactive transition is up to us. We ourselves must repeatedly reposition the views at every stage of the gesture, and when the gesture ends, we ourselves must animate them either into their final position or back into their initial position. We will no longer need an animateTransition: method (it must be present, to satisfy the protocol requirements, but it can be empty); all the logic of initializing, positioning, and finalizing the moving views must be effectively deconstructed and folded into the various stages of our gesture recognizer action handler. At every stage, we must keep talking to the transition context itself.

The resulting code is verbose, and can be difficult to express in a compact or object-oriented way. However, it is more powerful, more flexible, and possibly more reliable than using a percent driver. We don’t need to rely on the percent driver’s trick of using a frozen animation; we can reposition the actual views (or their snapshots) as the gesture proceeds.

I’ll just sketch out how to rewrite our tab bar controller interactive transition without a percent driver. We have our gesture recognizer(s) as before. When the gesture begins, the .Began section of the action handler triggers the transition as before. The delegate is asked for an animation controller and an interaction controller; the interaction controller is now not a percent driver, but some object that we will supply — let’s say it’s self.

The result is that, in our role as adopter of the UIViewControllerInteractiveTransitioning protocol, our startInteractiveTransition: is called. Here, we set up the initial conditions of the transition, putting the views into place, and storing a reference to the transitionContext in a property where our gesture recognizer action handler can access it:

func startInteractiveTransition(
    transitionContext: UIViewControllerContextTransitioning){
    // store transition context so the gesture recognizer can get at it
    self.context = transitionContext
    // ... set up initial conditions ...
    // ... store any additional instance variables ...
    self.r1end = r1end
    self.r2start = r2start
}

Our gesture recognizer action handler is called again, repeatedly, at the .Changed stage. We keep repositioning our views in accordance with the progress of the interactive gesture. At the same time, we keep informing the transition context of that progress:

case .Changed:
    // ... calculate progress (percent)
    // ... put views into position corresponding to current "frame" ...
    // ... and finally, notify the transition context
    v1.frame = // whatever
    v2.frame = // whatever
    tc.updateInteractiveTransition(percent)

(Why must we call updateInteractiveTransition: throughout the progress of the gesture? For a tab bar controller’s transition, this call has little or no significance. But in the case, say, of a navigation controller, the animation has a component separate from what you’re doing — the change in the appearance of the navigation bar, as the old title departs and the new title arrives and so forth. The transition context needs to coordinate that animation with the interactive gesture and with your animation. So you need to keep telling it where things are in the course of the interaction.)

Finally, our gesture recognizer action handler is called one last time, at the .Ended stage. We now animate our views the rest of the way, or else back to the start, and call either finishInteractiveTransition or cancelInteractiveTransition followed by completeTransition: with the appropriate argument:

case .Ended:
    if percent > 0.5 {
        UIView.animateWithDuration(0.2, animations:{
            v1.frame = self.r1end
            v2.frame = r2end
            }, completion: { _ in
                tc.finishInteractiveTransition()
                tc.completeTransition(true)
        })
    }
    else {
        UIView.animateWithDuration(0.2, animations:{
            v1.frame = r1start
            v2.frame = self.r2start
            }, completion: { _ in
                tc.cancelInteractiveTransition()
                tc.completeTransition(false)
        })
    }

Custom Presented View Controller Transition

Customizing what happens when a view controller is presented is more complex, and more powerful, than customizing a tab bar controller or navigation controller transition. With a presented view controller, you can customize not only the animation but also the final position of the presented view. Moreover, you can introduce extra views which remain in the scene until dismissal; for example, if the presented view is smaller than the presenting view and covers it only partially, you might add a dimming view between them, to darken the presenting view (just as a .FormSheet presentation does).

What the runtime must do while a view is being presented in order to make the presentation customizable is also more complex. There is no existing view to serve as the container view; therefore, when the presentation starts, the runtime must construct the container view and insert it into the interface and leave it there for only as long as the view remains presented. In the case of a .FullScreen presentation, the runtime must also rip the presenting view out of the interface and insert it into the container view, because you might want the presenting view to participate in the animation. For other styles of presentation, the container view is in front of the presenting view, which can’t be animated and is left in place as the presentation proceeds.

The work of customizing a presentation is distributed between two objects: the animation controller (or interaction controller) on the one hand, and the presentation controller on the other. The idea is that the animation controller should be responsible for only the animation, the movement of the presented view into its final position. The determination of that final position is the job of the presentation controller. The presentation controller is also responsible for inserting any extra views, such as a dimming view, into the container view; Apple says that the animation controller animates the content, while the presentation controller animates the “chrome.” This distribution of responsibilities may require some extra effort on your part, but in fact it also simplifies considerably the task of customizing the animation itself.

Customizing the animation

I’ll start with a situation where we don’t need to use the presentation controller: all we want to do is customize the animation part of a built-in presentation style. The steps are almost completely parallel to how we customized a tab bar controller animation:

  1. Give the presented view controller a delegate. Here, we assign to the presented view controller’s transitioningDelegate property an object adopting the UIViewControllerTransitioningDelegate protocol.
  2. The delegate will be asked for an animation controller, and will return an object adopting the UIViewControllerAnimatedTransitioning protocol. Unlike a tab bar controller or navigation controller, a presented view controller’s view undergoes two animations — the presentation and the dismissal — and therefore the delegate is asked separately for controllers:

    • animation⁠Controller⁠For⁠Presented⁠Controller:⁠presenting⁠Controller:​source⁠Controller:
    • interactionControllerForPresentation:
    • animationControllerForDismissedController:
    • interactionControllerForDismissal:

    You are free to customize just one animation, leaving the other at the default by not providing a controller for it.

  3. The animation controller will implement transitionDuration and animateTransition:.

To illustrate, let’s say we’re running on an iPad, and we want to present a view using the .FormSheet presentation style. But instead of using any of the built-in animation styles, we’ll have the presented view appear to grow from the middle of the screen. The only mildly tricky step is the first one. The problem is that the delegate must be assigned very early in the presented view controller’s life — before the presentation begins. But the presented view controller doesn’t exist before the presentation begins. The most reliable approach, therefore, is for the presented view controller to assign its own delegate in its own initializer:

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

On to step two. The transitioning delegate is asked for an animation controller; here, I’ll have it supply self once again, and I’ll do this only for the presentation, leaving the dismissal to use the default animation:

func animationControllerForPresentedController(
    presented: UIViewController,
    presentingController presenting: UIViewController,
    sourceController source: UIViewController)
    -> UIViewControllerAnimatedTransitioning? {
        return self
}

Finally, step three — the actual animation. This is extremely simple, especially because we don’t care or inquire about the From view controller, which remains in place during the presentation (indeed, its view isn’t even in the container view):

func transitionDuration(
    transitionContext: UIViewControllerContextTransitioning?)
     -> NSTimeInterval {
        return 0.4
}
func animateTransition(
    transitionContext: UIViewControllerContextTransitioning) {
        let vc2 = transitionContext.viewControllerForKey(
            UITransitionContextToViewControllerKey)
        let con = transitionContext.containerView()!
        let r2end = transitionContext.finalFrameForViewController(vc2!)
        let v2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
        v2.frame = r2end
        v2.transform = CGAffineTransformMakeScale(0.1, 0.1)
        v2.alpha = 0
        con.addSubview(v2)
        UIView.animateWithDuration(0.4, animations: {
            v2.alpha = 1
            v2.transform = CGAffineTransformIdentity
            }, completion: {
                _ in
                transitionContext.completeTransition(true)
            })
}

There is just one complication when you animate a presented view controller’s view. If you also animate the dismissal of that view, and if the animation controller is the same object so that the same animateTransition: implementation is called, the roles are reversed: on presentation, the presented view controller is the To view controller (UITransitionContextToViewControllerKey and UITransitionContextToViewKey), but on dismissal, it is the From view controller (UITransitionContextFromViewControllerKey and UITransitionContextFromViewKey). For a presentation that isn’t .FullScreen, the unused view is nil, so you can distinguish the cases by structuring your code like this:

let v1 = transitionContext.viewForKey(UITransitionContextFromViewKey)
let v2 = transitionContext.viewForKey(UITransitionContextToViewKey)
if let v2 = v2 { // presenting
    // ...
} else if let v1 = v1 {
    // ...
}

Customizing the presentation

Now let’s involve the presentation controller. This will require some additional steps:

  1. In addition to setting a transitioningDelegate, we set the presented view controller’s modalPresentationStyle to .Custom.
  2. The result of the preceding step is that the delegate is sent an additional message:

    • presentationControllerForPresentedViewController:​pre­sen­ting­View­Con­trol­ler:​sourceViewController:

    (The sourceViewController: parameter is what I have termed the “original presenter.”) Your mission is to return an instance of a custom UIPresentationController subclass which you have previously declared and implemented. This will then be the presented view controller’s presentation controller during the course of this presentation, from the time presentation begins to the time dismissal ends. You must create this instance by calling (directly or indirectly) the designated initializer:

    • init(presentedViewController:presentingViewController:)
  3. By overriding appropriate UIPresentationController methods in your UIPresentationController subclass, you participate in the presentation, setting the presented view controller’s position and adding “chrome” to the presentation as desired.

Tip

It is perfectly legal for the transitioning delegate to supply a custom presentation controller and no animation controller! In that case, a default animation will be performed, but the presented view will end up at the position your custom presentation controller dictates. This is a good example of the delightful “separation of powers” of the presentation controller and the animation controller.

The UIPresentationController has properties pointing to the presentingViewController as well the presentedViewController and the presentedView, plus the presentationStyle set by the presented view controller. It also obtains the containerView, which it subsequently communicates to the animation controller’s transition context. It has some methods that you can override in your subclass, but you only need to override the ones that require customization for your particular implementation:

frameOfPresentedViewInContainerView
Returns the final position of the presented view. The animation coordinator, if there is one, will obtain this frame through the transition context’s finalFrameForViewController: method and must cause the presented view to end up there (as you already know).
presentationTransitionWillBegin
presentationTransitionDidEnd
dismissalTransitionWillBegin
dismissalTransitionDidEnd
Use these events as signals to add or remove “chrome” (extra views) to the container view.
containerViewWillLayoutSubviews
containerViewDidLayoutSubviews
Use these layout events as signals to update the “chrome” views if needed.
shouldPresentInFullscreen
The default is to return true; returning false turns this presentation into a .CurrentContext presentation.
shouldRemovePresentersView
The default is to return false, except that of course it is true for a standard .FullScreen presentation, meaning that the presenting view is ripped out of the interface at the end of the presentation transition. You can return true for a custom presentation, but it would be rare to do this; even if the presented view completely covers the presenting view, there is no harm in leaving the presenting view in place.

A UIPresentationController is not a UIViewController, but it adopts some protocols that UIViewController adopts, and thus gets the same resizing-related messages that a UIViewController gets, as I described earlier in this chapter. It adopts UITraitEnvironment, meaning that it has a traitCollection and participates in the trait collection inheritance hierarchy, and receives the traitCollectionDidChange: message. It also adopts UIContentContainer, meaning that it receives willTransitionToTraitCollection:withTransitionCoordinator: and viewWillTransitionToSize:withTransitionCoordinator:.

In addition, a presentation controller functions as the parent of the presented view controller, and can override its inherited trait collection and respond to changes in its preferredContentSize, as I’ll explain later.

To illustrate the use of a custom presentation controller, I’ll expand the preceding example to implement a custom presentation style that looks like a .FormSheet even on an iPhone. The first step is to set the presentation style to .Custom at the same time that we set the transitioning delegate:

required init?(coder aDecoder: NSCoder) {
    super.init(coder:aDecoder)
    self.transitioningDelegate = self
    self.modalPresentationStyle = .Custom // *
}

The result (step two) is that the extra delegate method is called so that we can provide a custom presentation controller, and we do so:

func presentationControllerForPresentedViewController(
    presented: UIViewController,
    presentingViewController presenting: UIViewController,
    sourceViewController source: UIViewController)
    -> UIPresentationController? {
        let pc = MyPresentationController(
            presentedViewController: presented,
            presentingViewController: presenting)
        return pc
}

Everything else happens in our implementation of MyPresentationController. To make the presentation look like a .FormSheet, we inset the presented view’s frame:

override func frameOfPresentedViewInContainerView() -> CGRect {
    return super.frameOfPresentedViewInContainerView()
        .insetBy(dx: 40, dy: 40)
}

We could actually stop at this point! The presented view now appears in the correct position. However, the presenting view is appearing undimmed behind it. Let’s add dimming, by inserting a translucent dimming view into the container view. Note that we are careful to deal with the possibility of subsequent rotation:

override func presentationTransitionWillBegin() {
    let con = self.containerView!
    let shadow = UIView(frame:con.bounds)
    shadow.backgroundColor = UIColor(white:0, alpha:0.4)
    con.insertSubview(shadow, atIndex: 0)
    shadow.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
}

Again, this works perfectly, but now I don’t like what happens when the presented view is dismissed: the dimming view vanishes suddenly at the end of the dismissal. I’d rather have the dimming view fade out, and I’d like it to fade out in coordination with the dismissal animation. The way to arrange that is through the object vended by the presented view controller’s transitionCoordinator method. This object is just like the transition coordinator I’ve already discussed earlier in this chapter in connection with resizing events and rotation: in particular, we can call its animateAlongsideTransition: method to add our own animation:

override func dismissalTransitionWillBegin() {
    let con = self.containerView!
    let shadow = con.subviews[0]
    let tc = self.presentedViewController.transitionCoordinator()!
    tc.animateAlongsideTransition({
        _ in
        shadow.alpha = 0
        }, completion: nil)
}

Once again, we could stop at this point. But I’d like to add a further refinement. A .FormSheet view has rounded corners. I’d like to make our presented view look the same way:

override func presentedView() -> UIView? {
    let v = super.presentedView()!
    v.layer.cornerRadius = 6
    v.layer.masksToBounds = true
    return v
}

Finally, for completeness, it would be nice, during presentation, to dim the appearance of any button titles and other tinted interface elements visible through the dimming view, to emphasize that they are disabled:

override func presentationTransitionDidEnd(completed: Bool) {
    let vc = self.presentingViewController
    let v = vc.view
    v.tintAdjustmentMode = .Dimmed
}
override func dismissalTransitionDidEnd(completed: Bool) {
    let vc = self.presentingViewController
    let v = vc.view
    v.tintAdjustmentMode = .Automatic
}

Transition Coordinator

A UIViewController, as I mentioned in the previous section, has a transitionCoordinator method, which yields an object adopting the UIViewControllerTransitionCoordinator protocol. This object, the transition coordinator, is a kind of wrapper around the current transition context, and also adopts the UIViewControllerTransitionCoordinatorContext protocol, just like the transition context. Thus, in effect, view controllers can find out about the transition they are involved in.

In addition to the methods that it implements by virtue of adopting the UIViewControllerTransitionCoordinatorContext protocol, a transition coordinator implements the following methods:

animateAlongsideTransition:completion:

Takes an animation block and a completion block. The animation you supply is incorporated into the transition coordinator’s animation. Returns a Bool, informing you in case your commands couldn’t be animated. Both blocks receive the transition context as a parameter. (See also Responding to rotation, where I discussed this method in connection with rotation.)

A view controller’s use of this method will typically be to add animation of its view’s internal interface as part of a transition animation. Observe that this works equally for a custom animation or a built-in animation; in fact, the point is that the view controller can behave agnostically with regard to how its own view is being animated. In this example, a presented view controller animates part of its interface into place as the animation proceeds (whatever that animation may be):

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    if let tc = self.transitionCoordinator() {
        tc.animateAlongsideTransition({
            _ in
            self.buttonTopConstraint.constant += 200
            self.view.layoutIfNeeded()
        }, completion: nil)
    }
}
animateAlongsideTransitionInView:animation:completion:
Just like the previous method, except that the animated view can be outside the container view.
notifyWhenInteractionEndsUsingBlock:

Called at the moment the user abandons an interactive gesture and the transition is about to be either completed or cancelled. This is useful particularly if the interactive transition is being cancelled, as it may well be that what your view controller wants to do will differ in this situation. The parameter to the block is the transition context.

In this example, a navigation controller has pushed a view controller, and now the user is popping it interactively (using the default drag-from-the-left-edge gesture). If the user cancels, the back view controller can hear about it, like this:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    guard let tc = self.transitionCoordinator() else {return}
    guard tc.initiallyInteractive() else {return}
    tc.notifyWhenInteractionEndsUsingBlock {
        context in
        if context.isCancelled() {
            // ...
        }
    }
}

Warning

I have not found any occasion when the child of a tab bar controller has a non-nil transition coordinator. This feels like a bug.

Page View Controller

A page view controller (UIPageViewController) displays its child view controller’s view. The user, by a gesture, can navigate in one direction or the other to see the next or the previous child view controller’s view, successively — like turning the pages of a book. In reality, the page view controller has only one child view controller at a time (or, if so configured, two at a time); it navigates by releasing its existing child view controller and replacing it with another.

Think of a page view controller as being like a book that pretends to have multiple pages, but in fact has just one page at a time, and replaces that page with another when the reader “turns the page.” This is a very efficient architecture: it makes no difference whether the page view controller lets the user page through three pages or ten thousand pages, because the pages are created in real time, on demand, and each page exists only as long as the user is looking at it.

Preparing a Page View Controller

To create a UIPageViewController, use its designated initializer:

  • init(transitionStyle:navigationOrientation:options:)

Here’s what the parameters mean:

transitionStyle:

The animation style during navigation (UIPageViewControllerTransitionStyle). Your choices are:

  • .PageCurl
  • .Scroll (sliding)
navigationOrientation:

The direction of navigation (UIPageViewControllerNavigationOrientation). Your choices are:

  • .Horizontal
  • .Vertical
options:

A dictionary. Possible keys are:

UIPageViewControllerOptionSpineLocationKey

If you’re using the page curl transition, this is the position of the pivot line around which those page curl transitions rotate. The value (UIPageViewControllerSpineLocation) is one of the following:

  • .Min (left or top)
  • .Mid (middle; two pages are shown at once)
  • .Max (right or bottom)
UIPageViewControllerOptionInterPageSpacingKey
If you’re using the scroll transition, this is the spacing between successive pages, visible as a gap during the transition (the default is 0).

You configure the page view controller’s initial content by handing it its initial child view controller(s). You do that by calling this method:

  • setViewControllers:direction:animated:completion:

Here’s what the parameters mean:

viewControllers:
An array of one view controller — unless you’re using the page curl transition and the .Mid spine location, in which case it’s an array of two view controllers.
direction:

The animation direction (UIPageViewControllerNavigationDirection). This probably won’t matter when you’re assigning the page view controller its initial content, as you are not likely to want any animation. Possible values are:

  • .Forward
  • .Backward
animated:, completion:
A Bool and a completion handler.

To allow the user to page through the page view controller, and to supply the page view controller with a new page at that time, you also assign the page view controller a dataSource, which should conform to the UIPageViewControllerDataSource protocol. The dataSource is told whenever the user starts to change pages, and should respond by immediately providing another view controller whose view will constitute the new page. Typically, the data source will create this view controller on the spot.

Here’s a minimal example. Each page in the page view controller is to portray an image of a named Pep Boy. The first question is where the pages will come from. My data model consists of an array (self.pep) of the string names of the three Pep Boys, along with three eponymous image files in my app bundle portraying each Pep Boy. I’ve also got a UIViewController subclass called Pep, capable of displaying a Pep Boy. I initialize a Pep object with the designated initializer init(pepBoy:), supplying the name of a Pep Boy from the array; the Pep object sets its own boy property:

init(pepBoy boy:String) {
    self.boy = boy
    super.init(nibName: "Pep", bundle: nil)
}

Pep’s viewDidLoad then fetches the corresponding image and assigns it as the image of a UIImageView within its own view:

override func viewDidLoad() {
    super.viewDidLoad()
    self.pic.image = UIImage(named:"(self.boy.lowercaseString).jpg")
}

Here’s how I create the page view controller itself (in my app delegate):

// make a page view controller
let pvc = UIPageViewController(
    transitionStyle: .Scroll,
    navigationOrientation: .Horizontal, options: nil)
// give it an initial page
let page = Pep(pepBoy: self.pep[0])
pvc.setViewControllers(
    [page], direction: .Forward, animated: false, completion: nil)
// give it a data source
pvc.dataSource = self
// puts its view into the interface
self.window!.rootViewController = pvc

The page view controller is a UIViewController, and its view must get into the interface by standard means. You can make the page view controller the window’s rootViewController, as I do here; you can make it a presented view controller; you can make it a child view controller of a tab bar controller or a navigation controller. If you want the page view controller’s view to be a subview of a custom view controller’s view, that view controller must be a custom container view controller, as I’ll describe later.

Page View Controller Navigation

We now have a page view controller’s view in our interface, itself containing and displaying the view of a Pep view controller that is its child. In theory, we also have three pages, because we have three Pep Boys and their images — but the page view controller knows about only one of them. Just as with a navigation controller, you don’t supply (or even create) a page until the moment comes to navigate to it. When that happens, one of these data source methods will be called:

  • pageViewController:viewControllerAfterViewController:
  • pageViewController:viewControllerBeforeViewController:

The job of those methods is to return the requested successive view controller. You’ll need a strategy for doing that; the strategy you devise will depend on how your model maintains the data.

My data is an array of unique strings, so all I have to do is find the previous name or the next name in the array. Here’s one of my data source methods:

func pageViewController(pageViewController: UIPageViewController,
    viewControllerAfterViewController viewController: UIViewController)
    -> UIViewController? {
        let boy = (viewController as! Pep).boy
        let ix = self.pep.indexOf(boy)! + 1
        if ix >= self.pep.count {
            return nil
        }
        return Pep(pepBoy: self.pep[ix])
}

You can also, at any time, call setViewControllers:... to change programmatically what page is being displayed, possibly with animation.

Page indicator

If you’re using the scroll style, the page view controller will optionally display a page indicator (a UIPageControl, see Chapter 12). The user can look at this to get a sense of what page we’re on, and can tap to the left or right of it to navigate. To get the page indicator, you must implement two more data source methods; they are consulted in response to setViewControllers:.... We called that method initially to configure the page view controller; if we never call it again (because the user simply keeps navigating to the next or previous page), these data source methods won’t be called again either, but they don’t need to be: the page view controller will thenceforth keep track of the current index on its own. Here’s my implementation for the Pep Boy example:

func presentationCountForPageViewController(
    pageViewController: UIPageViewController) -> Int {
        return self.pep.count
}
func presentationIndexForPageViewController(
    pvc: UIPageViewController) -> Int {
        let page = pvc.viewControllers![0] as! Pep
        let boy = page.boy
        return self.pep.indexOf(boy)!
}

Unfortunately, the page view controller’s page indicator by default has white dots and a clear background, so it is invisible in front of a white background. You’ll want to customize it to change that. Unfortunately, there is no direct access to it, so it’s simplest to use the appearance proxy (Chapter 12). For example:

let proxy = UIPageControl.appearance()
proxy.pageIndicatorTintColor = UIColor.redColor().colorWithAlphaComponent(0.6)
proxy.currentPageIndicatorTintColor = UIColor.redColor()
proxy.backgroundColor = UIColor.yellowColor()

Navigation gestures

If you’ve assigned the page view controller the .PageCurl transition style, the user can ask for navigation by tapping at either edge of the view or by dragging across the view. These gestures are detected through two gesture recognizers, which you can access through the page view controller’s gestureRecognizers property. The documentation suggests that you might change where the user can tap or drag by attaching them to a different view, and other customizations are possible as well. In this code, I change a .PageCurl page view controller’s behavior so that the user must double tap to request navigation:

for g in pvc.gestureRecognizers {
    if let g = g as? UITapGestureRecognizer {
        g.numberOfTapsRequired = 2
    }
}

Of course you are also free to add to the user’s stock of gestures for requesting navigation. You can supply any controls or gesture recognizers that make sense for your app, and respond by calling setViewControllers:.... For example, if you’re using the .Scroll transition style, there’s no tap gesture recognizer, so the user can’t tap at either edge of the page view controller’s view to request navigation. Let’s change that. I’ve added invisible views at either edge of my Pep view controller’s view, with tap gesture recognizers attached. When the user taps, the tap gesture recognizer fires, and the action handler posts a notification whose object is the tap gesture recognizer:

@IBAction func tap (sender: UIGestureRecognizer?) {
    NSNotificationCenter.defaultCenter().postNotificationName(
        "tap", object: sender)
}

I receive this notification and use the tap gesture recognizer’s view’s tag to learn which view it is; I then navigate accordingly (n is the notification, pvc is the page view controller):

let g = n.object as! UIGestureRecognizer
let which = g.view!.tag
let vc0 = pvc.viewControllers![0]
let vc = (which == 0 ?
    self.pageViewController(pvc, viewControllerBeforeViewController: vc0) :
    self.pageViewController(pvc, viewControllerAfterViewController: vc0))
if vc == nil {
    return
}
let dir : UIPageViewControllerNavigationDirection =
    which == 0 ? .Reverse : .Forward
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
pvc.setViewControllers([vc!], direction: dir, animated: true, completion: {
    _ in
    UIApplication.sharedApplication().endIgnoringInteractionEvents()
})

Other Page View Controller Configurations

It is possible to assign a page view controller a delegate (UIPageViewControllerDelegate), which gets an event when the user starts turning the page and when the user finishes turning the page, and can change the spine location dynamically in response to a change in device orientation. As with a tab bar controller’s delegate or a navigation controller’s delegate, a page view controller’s delegate also gets messages allowing it to specify the page view controller’s rotation policy, so there’s no need to subclass UIPageViewController solely for that purpose.

One further bit of configuration applicable to a .PageCurl page view controller is the doubleSided property. If it is true, the next page occupies the back of the previous page. The default is false, unless the spine is in the middle, in which case it’s true and can’t be changed. Your only option here, therefore, is to set it to true when the spine isn’t in the middle, and in that case the back of each page would be a sort of throwaway page, glimpsed by the user during the page curl animation.

A page view controller in a storyboard lets you configure its transition style, navigation orientation, page spacing, spine location, and doubleSided property. (It also has delegate and data source outlets, but you’re not allowed to connect them to other view controllers, because you can’t draw an outlet from one scene to another in a storyboard.) It has no child view controller relationship, so you can’t set the page view controller’s initial child view controller in the storyboard; you’ll have to complete the page view controller’s initial configuration in code.

Container View Controllers

UITabBarController, UINavigationController, and UIPageViewController are built-in parent view controllers: you hand them a child view controller and they put that child view controller’s view into the interface for you, inside their own view. What if you want your own view controller to do the same thing?

Back in iOS 3 and 4, that was illegal; the only way a view controller’s view could get into the interface was if a built-in parent view controller put it there. You could put a view into the interface, of course — but not a view controller’s view. (Naturally, developers ignored this restriction, and got themselves into all kinds of difficulties.) In iOS 5, Apple introduced a coherent way for you to create your own parent view controllers, which can legally manage child view controllers and put their views into the interface. A custom parent view controller of this sort is called a container view controller.

Container view controllers give you the flexibility and power to construct the kind of hierarchy shown in Figure 6-4 from your own view controllers and their views, nesting them in what whatever manner and degree you like. Your own view controller becomes like one of the built-in parent view controllers, except that you get to define what it does — what it means for a view controller to be a child of this kind of parent view controller, how many children it has, which of its children’s views appear in the interface and where they appear, and so on. A container view controller can also participate actively in the business of trait collection inheritance and view resizing.

Adding and Removing Children

A view controller has a childViewControllers array. This is what gives it the power to be a parent! You must not, however, just wantonly populate this array. A child view controller needs to receive certain definite events at particular moments:

  • As it becomes a child view controller
  • As its view is added to and removed from the interface
  • As it ceases to be a child view controller

Therefore, to act as a parent view controller, your UIViewController subclass must fulfill certain responsibilities:

Adding a child

When a view controller is to become your view controller’s child, your view controller must do these things, in this order:

  1. Send addChildViewController: to itself, with the child as argument. The child is automatically added to your childViewControllers array and is retained.
  2. Get the child view controller’s view into the interface (as a subview of your view controller’s view), if that’s what adding a child view controller means.
  3. Send didMoveToParentViewController: to the child with your view controller as its argument.
Removing a child

When a view controller is to cease being your view controller’s child, your view controller must do these things, in this order:

  1. Send willMoveToParentViewController: to the child with a nil argument.
  2. Remove the child view controller’s view from your interface.
  3. Send removeFromParentViewController to the child. The child is automatically removed from your childViewControllers array and is released.

This is a clumsy and rather confusing dance. The underlying reason for it is that a child view controller must always receive willMoveToParentViewController: followed by didMoveToParentViewController: (and your own child view controllers can take advantage of these events however you like). But it turns out that you don’t always send both these messages explicitly, because:

  • addChildViewController: sends willMoveToParentViewController: for you automatically.
  • removeFromParentViewController sends didMoveToParentViewController: for you automatically.

Thus, in each case you must send manually the other message, the one that adding or removing a child view controller doesn’t send for you — and of course you must send it so that everything happens in the correct order, as dictated by the rules I just listed.

Example 6-1 provides a schematic approach for how to obtain an initial child view controller and put its view into the interface, where no child view controller’s view was previously. (Alternatively, a storyboard can do this work for you, with no code, as I’ll explain later in this chapter.)

Example 6-1. Adding an initial child view controller
let vc = // whatever; this is the initial child view controller
self.addChildViewController(vc) // "will" called for us
vc.view.frame = // whatever, or use constraints
// insert view into interface between "will" and "did"
self.view.addSubview(vc.view)
// when we call add, we must call "did" afterwards
vc.didMoveToParentViewController(self)

The next question is how to replace one child view controller’s view in the interface with another (comparable to how UITabBarController behaves when a different tab bar item is selected). The simplest, most convenient way is for the parent view controller to send itself this message:

  • transitionFromViewController:toViewController:duration:options:​animations:​completion:

That method manages the stages in good order, adding one child view controller’s view to the interface before the transition and removing the other child view controller’s view from the interface after the transition, and seeing to it that the child view controllers receive lifetime events (such as viewWillAppear:) at the right moment. Here’s what the last three arguments are for:

options:
A bitmask (UIViewAnimationOptions) comprising the same possible options that apply to any block-based view transition (see Transitions).
animations:
A block that may be used for additional view animations, besides the transition animation specified in the options: argument. Alternatively, if none of the built-in transition animations is suitable, you can animate the views yourself here; they are both in the interface during this block.
completion:
This block will be important if the transition involves the removal or addition of a child view controller. At the time when transitionFromViewController:toViewController:... is called, both view controllers must be children of the parent view controller; so if you’re going to remove one of the view controllers as a child, you’ll do it in the completion: block. Similarly, if you owe a new child view controller a didMoveToParentViewController: call, you’ll use the completion: block to fulfill that debt.

Here’s an example. To keep things simple, suppose that our view controller has just one child view controller at a time, and displays the view of that child view controller within its own view. So let’s say that when our view controller is handed a new child view controller, it substitutes that new child view controller for the old child view controller and replaces the old child view controller’s view with the new child view controller’s view. Here’s code that does that correctly; the view controllers are fromvc and tovc:

// we have already been handed the new view controller
// set up the new view controller's view's frame
tovc.view.frame = // ... whatever
// must have both as children before we can transition between them
self.addChildViewController(tovc) // "will" called for us
// note: when we call remove, we must call "will" (with nil) beforehand
fromvc.willMoveToParentViewController(nil)
// then perform the transition
self.transitionFromViewController(fromvc,
    toViewController:tovc,
    duration:0.4,
    options:.TransitionFlipFromLeft,
    animations:nil,
    completion:{
        _ in
        // finally, finish up
        // note: when we call add, we must call "did" afterwards
        tovc.didMoveToParentViewController(self)
        fromvc.removeFromParentViewController() // "did" called for us
    })

If we’re using constraints to position the new child view controller’s view, where will we set up those constraints? Before transitionFromViewController:... is too soon, as the new child view controller’s view is not yet in the interface. The completion: block is too late: if the view is added with no constraints, it will have no initial size or position, so the animation will be performed and then the view will suddenly seem to pop into existence as we provide its constraints. The animations: block turns out to be a very good place:

// must have both as children before we can transition between them
self.addChildViewController(tovc) // "will" called for us
// note: when we call remove, we must call "will" (with nil) beforehand
fromvc.willMoveToParentViewController(nil)
// then perform the transition
self.transitionFromViewController(fromvc,
    toViewController:tovc,
    duration:0.4,
    options:.TransitionFlipFromLeft,
    animations: {
        tovc.view.translatesAutoresizingMaskIntoConstraints = false
        // ... configure tovc.view constraints here ...
    },
    completion:{
        _ in
        // finally, finish up
        // note: when we call add, we must call "did" afterwards
        tovc.didMoveToParentViewController(self)
        fromvc.removeFromParentViewController() // "did" called for us
    })

Alternatively, you can use a layout-related event, such as viewWillLayoutSubviews; still, I prefer the animations: block, as it is called just once at exactly the right moment.

If the built-in transition animations are unsuitable, you can set the options: argument to .None and provide your own animation in the animations: block, at which time both views are in the interface. In this example, I animate a substitute view (an image view showing a snapshot of tovc.view) to grow from the top left corner; then I configure the real view’s constraints and remove the substitute:

// tovc.view.frame is already set
UIGraphicsBeginImageContextWithOptions(tovc.view.bounds.size, true, 0)
tovc.view.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let iv = UIImageView(image:im)
iv.frame = CGRectZero
self.view.addSubview(iv)
tovc.view.alpha = 0
// must have both as children before we can transition between them
self.addChildViewController(tovc) // "will" called for us
// note: when we call remove, we must call "will" (with nil) beforehand
fromvc.willMoveToParentViewController(nil)
// then perform the transition
self.transitionFromViewController(fromvc,
    toViewController:tovc,
    duration:0.4,
    options:.TransitionNone,
    animations: {
        iv.frame = tovc.view.frame // animate bounds change
        // ... configure tovc.view constraints here ...
    },
    completion:{
        _ in
        tovc.view.alpha = 1
        iv.removeFromSuperview()
        // finally, finish up
        // note: when we call add, we must call "did" afterwards
        tovc.didMoveToParentViewController(self)
        fromvc.removeFromParentViewController() // "did" called for us
    })

If your parent view controller is going to be consulted about the status bar (whether it should be shown or hidden, and if shown, whether its text should be light or dark), it can elect to defer the decision to one of its children, by implementing these methods (just as a UITabBarController does):

  • childViewControllerForStatusBarStyle
  • childViewControllerForStatusBarHidden

Container View Controllers, Traits, and Resizing

A container view controller participates in trait collection inheritance and view resizing. In fact, you may well insert a container view controller into your view controller hierarchy for no other purpose than to engage in such participation.

Thus far, I have treated trait collection inheritance as immutable; and for the most part, it is. A UITraitEnvironment (an object with a traitCollection property: UIScreen, UIView, UIViewController, or UIPresentationController) ultimately gets its trait collection from the environment, and the environment is simply a fact. Nevertheless, a parent view controller has the amazing ability to lie to a child view controller about the environment, thanks to this method:

  • setOverrideTraitCollection:forChildViewController:

The first parameter is a UITraitCollection that will be combined with the inherited trait collection and communicated to the specified child.

This is a UIViewController instance method, so only view controllers have this mighty power. Moreover, you have to specify a child view controller, so only parent view controllers have this mighty power. However, UIPresentationController has a similar power, through its overrideTraitCollection property; setting this property combines the override trait collection with the inherited collection and communicates it to the presented view controller.

Why would you want to do such a thing as lie to a child view controller about its environment? Well, imagine that we’re writing an iPad app, and we have a view controller whose view can appear either fullscreen or as a small subview of a parent view controller’s main view. The view’s interface might need to be different when it appears in the smaller size. You could configure that using size classes (conditional constraints) in the nib editor, with one interface for a .Regular horizontal size class (iPad) and another interface for a .Compact horizontal size class (iPhone). Then, when the view is to appear in its smaller size, we lie to its view controller and tell it that this is an iPhone:

let vc = // the view controller we're going to use as a child
self.addChildViewController(vc)
let tc = UITraitCollection(horizontalSizeClass: .Compact)
self.setOverrideTraitCollection(tc, forChildViewController: vc) // heh heh
vc.view.frame = // whatever
self.view.addSubview(vc.view)
vc.didMoveToParentViewController(self)

Apple’s own motivating example involves UISplitViewController, a class that behaves differently depending on its trait environment. For example, by lying to a split view controller and making it believe we’re on an iPad, you can cause the split view controller to look like a split view controller (displaying two subviews, such as a master table view and a detail view) even on an iPhone. I’ll talk more about that in Chapter 9.

A parent view controller sets the size of a child view controller’s view. A child view controller, however, can express a preference as to what size it would like its view to be, by setting its own preferredContentSize property. The chief purpose of this property is to be consulted by a parent view controller when this view controller is its child. This property is a preference and no more; no law says that the parent must consult the child, or that the parent must obey the child’s preference.

If a view controller’s preferredContentSize is set while it is a child view controller, the runtime automatically communicates this fact to the parent view controller, by calling this UIContentContainer method:

  • preferredContentSizeDidChangeForChildContentContainer:

The parent view controller may implement this method to consult the child’s preferredContentSize and change the child’s view’s size in response. Again, no law requires the parent to do this. This method, and the preferredContentSize property, are ways of allowing a child view controller a voice in its view’s size; it is the parent who dictates what that size will actually be.

A parent view controller, as an adopter of the UIContentContainer protocol (along with UIPresentationController), is also responsible for communicating to its children that their sizes are changing and what their new sizes will be. It is the parent view controller’s duty to implement this method:

sizeForChildContentContainer:withParentContainerSize:
Should be implemented to return each child view controller’s correct size at any moment. Failure to implement this method will cause the child view controller to be handed the wrong size in its implementation of viewWillTransitionToSize:withTransitionCoordinator: — it will be given the parent’s new size rather than its own new size!

If your parent view controller implements viewWillTransitionToSize:withTransitionCoordinator:, calling super is sufficient to get the same message passed on to the children. This works even if your implementation explicitly changed the size of a child view controller at this time, provided that you implemented sizeForChildContentContainer:withParentContainerSize: to return the new size.

Storyboards

Throughout this chapter, I’ve been describing how to create a view controller and present it or make it another view controller’s child manually, entirely in code. But if you’re using a storyboard, you will often (or always) allow the storyboard to do those things for you automatically. A storyboard can be helpful and convenient in this regard, though not, perhaps, for the reasons one might expect. It doesn’t necessarily reduce the amount of code you’ll have to write; indeed, in some cases using a storyboard may compel you to write more code, and in a less readable and maintainable way, than if you were creating your view controllers manually. But a storyboard does clarify the relationships between your view controllers over the course of your app’s lifetime. Instead of having to hunt around in each of your classes to see which class creates which view controller and when, you can view and manage the chain of view controller creation graphically in the nib editor. Figure 6-9 shows the storyboard of a small test app.

The storyboard of an app
Figure 6-9. The storyboard of an app

A storyboard, as I’ve already explained, is basically a collection of view controller nibs (scenes) and view nibs. Each view controller is instantiated from its own nib, as needed, and will then obtain its view, as needed — typically from a view nib that you’ve configured in the same storyboard by editing the view controller’s view. I described this process in detail, and listed the ways in which a view controller in a storyboard can be instantiated, in Storyboard-Instantiated View Controller.

One of those ways is that your code could instantiate a view controller directly from a storyboard, by calling instantiateInitialViewController or instantiateViewControllerWithIdentifier:. The other three ways are automatic:

Initial view controller
If your app has a main storyboard, as specified by its Info.plist, that storyboard’s initial view controller will be instantiated and assigned as the window’s rootViewController automatically as the app launches. To specify that a view controller is a storyboard’s initial view controller, check the “Is Initial View Controller” checkbox in its Attributes inspector. This will cause any existing initial view controller to lose its initial view controller status. The initial view controller is distinguished graphically in the canvas by an arrow pointing to it from the left, and in the document outline by the presence of the Storyboard Entry Point.
Relationship

Two built-in parent view controllers can specify their children directly in the storyboard, setting their viewControllers array:

  • UITabBarController can specify multiple children.
  • UINavigationController can specify its single initial child (its root view controller).

To add a view controller as a viewControllers child to a parent view controller, Control-drag from the parent view controller to the child view controller; in the little HUD that appears, choose (under Relationship Segue) “view controllers” for a UITabBarController, or “root view controller” for a UINavigationController. The result is a relationship whose source is the parent and whose destination is the child. The destination view controller will be instantiated automatically when the source view controller is instantiated, and will be assigned into its viewControllers array, thus making it a child and retaining it.

Triggered segue

A triggered segue configures a future situation, when the segue will be triggered. At that time, one view controller that already exists will cause the instantiation of another, bringing the latter into existence. Two types of triggered segue are particularly common; they have special names in the nib editor if your storyboard uses size classes:

Show (without size classes: “push”)

The future view controller will be pushed onto the stack of the navigation controller of which the existing view controller is already a child.

The name “show” comes from the showViewController:sender: method, which pushes a view controller onto the parent navigation controller if there is one, but behaves adaptively if there is not (I’ll talk more about that in Chapter 9).

Present modally (without size classes: “modal”)
The future view controller will be a presented view controller (and the existing view controller will be its original presenter).

Unlike a relationship, a triggered segue does not have to emanate from a view controller; it can emanate from certain kinds of gesture recognizer, or from an appropriate view (such as a button or a table view cell) in the first view controller’s view. This is a graphical shorthand signifying that the segue should be triggered, bringing the second view controller into existence, when a tap or other gesture occurs.

To create a triggered segue, Control-drag from the view in the first view controller, or from the first view controller itself, to the second view controller. In the little HUD that appears, choose the type of segue you want (listed under Action Segue if you dragged from a button, or Manual Segue if you dragged from the view controller).

Warning

Apple’s segue nomenclature is confusing and inconsistent, and Apple itself seems unsettled by its own choice of names. Is a relationship a kind of segue, as in the HUD (“relationship segue”) or a different kind of connection altogether, as in the document outline (“relationship”)? Is a triggered segue a triggered segue, as in the WWDC 2015 videos, a manual segue or action segue, as in the HUD, or simply a segue, as in the document outline? Even the individual segue names are in doubt: in the Segue pop-up menu in the Attributes inspector, a push segue is called “Show (e.g. Push)” — a clear case of namer’s remorse.

Triggered Segues

A triggered segue (or simply segue), such as a push segue (show) or a modal segue (present modally), is a full-fledged object, an instance of UIStoryboardSegue (or your custom subclass thereof), and it can be configured in the nib editor through its Attributes inspector. In a storyboard, however, it is not a nib object, in the sense that it is not instantiated by the loading of a nib, and it cannot be pointed to by an outlet. Rather, it will be instantiated when the segue is triggered, at which time its designated initializer will be called, namely init(identifier:source:destination:).

A segue’s source and destination are the two view controllers between which it runs. The segue is directional, so the source and destination are clearly distinguished. The source view controller is the one that will exist already, before the segue is triggered; the destination view controller will be instantiated together with the segue itself, when the segue is triggered.

A segue’s identifier is a string. You can set this string for a segue in a storyboard through its Attributes inspector; this can be useful when you want to trigger the segue manually in code (you’ll specify it by means of its identifier), or when you have code that can receive a segue as parameter and you need to distinguish which segue this is.

Triggered segue behavior

The default behavior of a segue, when it is triggered, is exactly the behavior of the corresponding manual transition described earlier in this chapter:

Push segue
The segue is going to call pushViewController:animated: for you (if we are in a navigation interface).
Modal segue
The segue is going to call presentViewController:animated:completion: for you. Therefore, the Attributes inspector for this kind of segue also lets you specify the Presentation (equivalent to the view controller’s modalPresentationStyle) and the Transition (equivalent to the view controller’s modalTransitionStyle); if you change these settings from Default, the segue will set that property of the destination view controller, overriding whatever setting the destination view controller already has. The Animates checkbox is effectively the same as the second argument in presentViewController:animated:completion:.

You can further customize a triggered segue’s behavior by providing your own UIStoryboardSegue subclass. The key thing is that you must implement your custom segue’s perform method, which will be called after the segue is triggered and instantiated, in order to do the actual transition from one view controller to another. New in Xcode 7 and iOS 9, you can do this even for a push segue or a modal segue: in the Attributes inspector for the segue, you specify your UIStoryboardSegue subclass, and in that subclass, you call super in your perform implementation. Let’s say, for example, that you want to add a custom transition animation to a modal segue. You can do this by writing a segue class that makes itself the destination controller’s transitioning delegate in its perform implementation before calling super:

class MyCoolSegue: UIStoryboardSegue {
    override func perform() {
        let dest = self.destinationViewController
        dest.modalPresentationStyle = .Custom
        dest.transitioningDelegate = self
        super.perform()
    }
}

The rest is then exactly as in Custom Presented View Controller Transition. For example, the segue might proceed to make itself the animation controller as well:

extension MyCoolSegue: UIViewControllerTransitioningDelegate {
    func animationControllerForPresentedController(
        presented: UIViewController,
        presentingController presenting: UIViewController,
        sourceController source: UIViewController)
        -> UIViewControllerAnimatedTransitioning? {
            return self
    }
    func animationControllerForDismissedController(
        dismissed: UIViewController)
        -> UIViewControllerAnimatedTransitioning? {
            return self
    }
}

It would then provide the custom animations:

extension MyCoolSegue: UIViewControllerAnimatedTransitioning {
    func transitionDuration(
        transitionContext: UIViewControllerContextTransitioning?)
        -> NSTimeInterval {
            return 0.8
    }
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        let vc1 = transitionContext.viewControllerForKey(
            UITransitionContextFromViewControllerKey)!
        let vc2 = transitionContext.viewControllerForKey(
            UITransitionContextToViewControllerKey)!
        let con = transitionContext.containerView()!
        let r1start = transitionContext.initialFrameForViewController(vc1)
        let r2end = transitionContext.finalFrameForViewController(vc2)
        if let v2 = transitionContext.viewForKey(
            UITransitionContextToViewKey) {
                // ... provide presenting animation ...
        } else if let v1 = transitionContext.viewForKey(
            UITransitionContextFromViewKey) {
                // ... provide dismissing animation ...
        }
    }
}

You can also create a completely custom segue. To do so, in the HUD when you Control-drag to create the segue, ask for a Custom segue, and then, in the Attributes inspector, specify your UIStoryboardSegue subclass. Again, you must override perform, but now you don’t call super — the whole transition is completely up to you. Your perform implementation can access the segue’s identifier, sourceViewController, and destinationViewController properties. The destinationViewController has already been instantiated, but that’s all; doing something with this view controller so as to make it a child view controller or presented view controller, retaining it and causing its view to appear in the interface, is entirely up to your code.

How a segue is triggered

A triggered segue will be triggered in one of two ways:

Through a user gesture
If a segue emanates from a gesture recognizer or from a tappable view, it will be triggered automatically when the tap or other gesture occurs. Your source view controller class can prevent this, if you have overridden shouldPerformSegueWithIdentifier:sender:. It returns a Bool (and the default is true), so if you don’t want this segue triggered on this occasion, return false.
In code
If a segue emanates from a view controller as a whole, then triggering it is up to your code. Send performSegueWithIdentifier:sender: to the source view controller. (In this case, shouldPerformSegueWithIdentifier:sender: will not be called.)

View controller communication

When a segue is triggered, the destination view controller is instantiated automatically; your code does not instantiate it. This raises a crucial question: how on earth are you going to communicate between the source view controller and the destination view controller? This, you’ll remember, was the subject of an earlier section of this chapter (Communication With a Presented View Controller), where I used this code as an example:

let svc = SecondViewController(
    nibName: "SecondViewController", bundle: nil)
svc.data = "This is very important data!"
svc.delegate = self
self.presentViewController(svc, animated:true, completion:nil)

In that code, the first view controller creates the second view controller, and therefore has a reference to it at that moment. Thus, it has an opportunity of passing along some data to it before presenting it. With a modal segue, however, the second view controller is instantiated for you, and the segue itself is going to call presentViewController:animated:completion:. So when and how will the first view controller be able to set svc.data and set itself as svc’s delegate?

The answer is that, after the segue has instantiated the second view controller but before it is performed, the source view controller is sent prepareForSegue:sender:. This is the moment when the source view controller and the destination view controller meet! The source view controller can thus perform configurations on the destination view controller, hand it data, and so forth. The source view controller can work out which segue is being triggered by examining the segue’s identifier and destinationViewController properties, and the sender is the interface object that was tapped to trigger the segue (or, if performSegueWithIdentifier:sender: was called in code, whatever object was supplied as the sender: argument).

This solves the communication problem, though in a clumsy way; prepareForSegue:sender: feels like a blunt instrument. The destinationViewController arrives typed as a generic UIViewController, and it is up to your code to know its actual type, cast it, and configure it. Moreover, if more than one segue emanates from a view controller, they are all bottlenecked through the same prepareForSegue:sender: implementation, which thus devolves into an ugly collection of conditions to distinguish them.

Container Views and Embed Segues

As I mentioned earlier, the only parent view controllers for which you can create relationship segues specifying their children in a storyboard are the built-in UITabBarController and UINavigationController. That’s because the nib editor understands how they work. If you write your own custom container view controller, the nib editor doesn’t even know that your view controller is a container view controller, so it can’t be the source of a relationship segue.

Nevertheless, you can perform some initial parent–child configuration of your custom container view controller in a storyboard, if your situation conforms to these assumptions:

  • Your parent view controller will have one initial child view controller.
  • You want the child view controller’s view placed somewhere in the parent view controller’s view when the child view controller is instantiated.

Those are reasonable assumptions, and you can work around them if they don’t quite give the desired effect. For example, if your parent view controller is to have additional children, you can always add them later, in code; and if the child view controller’s view is not to be initially visible in the parent view controller’s view, you can always hide it.

To configure your parent view controller in a storyboard, drag a Container View object from the Object library into the parent view controller’s view in the canvas. The result is a view, together with an embed segue from it to an additional child view controller. You can then assign the child view controller its correct class in its Identity inspector. Alternatively, delete the child view controller, replace it with a different view controller, and Control-drag from the container view to this view controller and, in the HUD, specify an embed segue.

When an embed segue is triggered, the destination view controller is instantiated and made the source view controller’s child, and its view is placed exactly inside the container view as its subview. Thus, the container view is not only a way of generating the embed segue, but also a way of specifying where you want the child view controller’s view to go. The entire child-addition dance is performed correctly and automatically for you: addChildViewController: is called, the child’s view is put into the interface, and didMoveToParentViewController: is called.

Despite its superficial resemblance to a relationship segue, an embed segue is a triggered segue. It can have an identifier, and the standard messages are sent to the source view controller when the segue is triggered. Nevertheless, it has this similarity to a relationship: when the source (parent) view controller is instantiated, the runtime wants to trigger the segue automatically, instantiating the child view controller and embedding its view in the container view now. If that isn’t what you want, override shouldPerformSegueWithIdentifier:sender: in the parent view controller to return false for this segue, and call performSegueWithIdentifier:sender: later when you do want the child view controller instantiated.

The parent view controller is sent prepareForSegue:sender: before the child’s view loads. At this time, the child has not yet been added to the parent’s childViewControllers array. If you allow the segue to be triggered when the parent view controller is instantiated, then by the time the parent’s viewDidLoad is called, the child’s viewDidLoad has been called, the child has been added to the parent’s childViewControllers, and the child’s view is inside the parent’s view.

Subsequently replacing one child view controller’s view with another in the interface will require that you call transitionFromViewController:... just as you would have done if a storyboard weren’t involved (as I described earlier in this chapter). Still, you can configure this through a storyboard as well, by using a custom segue and a UIStoryboardSegue subclass.

Storyboard References

New in Xcode 7 and iOS 9, when you create a triggered segue in the storyboard, you don’t have to Control-drag to a view controller as the destination; instead, you can Control-drag to a storyboard reference which you have previously placed in the canvas of this storyboard. A storyboard reference is a placeholder for a specific view controller. Thus, instead of a large and complicated network of segues running all over your storyboard, possibly crisscrossing in confusing ways, you can effectively jump through the storyboard reference to the actual view controller.

To specify what view controller a storyboard reference stands for, you need to perform two steps:

  1. Select the view controller and, in the Identity inspector, give it a Storyboard ID.
  2. Select the storyboard reference and, in the Attributes inspector, enter that same Storyboard ID value as its Referenced ID.

But wait — there’s more! The referenced view controller doesn’t even have to be in the same storyboard as the storyboard reference. You can use a storyboard reference to jump to a different storyboard. This allows you to organize your app’s interface into multiple storyboards. This was always possible, but loading any storyboards apart from the automatically loaded main storyboard was up to you (in code). With a storyboard reference that leads into a different storyboard, that storyboard is loaded automatically.

To configure a storyboard reference to refer to a view controller in a different storyboard, use the Storyboard pop-up menu in its Attributes inspector. The rule is that if you specify the Storyboard but not the Referenced ID, the storyboard reference stands for the target storyboard’s initial view controller (the one marked as the Storyboard Entry Point in that storyboard’s document outline). If you do specify the Referenced ID, then of course the storyboard reference stands for the view controller with that Storyboard ID in the target storyboard.

This is a welcome and long-anticipated feature, which should make storyboards much more convenient to use.

Unwind Segues

Storyboards and segues would appear to be useful only half the time, because segues are asymmetrical. There is a push segue but no pop segue. There is a present modally segue but no dismiss segue.

In a nutshell, you can’t use a normal segue to mean “go back.” Triggering a triggered segue instantiates the destination view controller; it creates a new view controller instance. But when dismissing a presented view controller or popping a pushed view controller, we don’t need any new view controller instances. We want to return, somehow, to an existing instance of a view controller.

Warning

A common mistake among beginners is to make a triggered segue from view controller A to view controller B, and then try to express the notion “go back” by making another triggered segue from view controller B to view controller A. The result, of course, is not presentation and subsequent dismissal, but presentation piled on presentation, or push piled on push, one view controller instantiated on top of another on top of another. Do not construct a cycle of segues. (Unfortunately, the nib editor doesn’t alert you to this mistake.)

The solution is an unwind segue. An unwind segue does let you express the notion “go back” in a storyboard. Basically, it lets you jump to any view controller that is already instantiated further up your view controller hierarchy, destroying the source view controller and any intervening view controllers in good order.

Creating an unwind segue

Before you can create an unwind segue, you must implement an unwind method in the class of any view controller represented in the storyboard. This should be a method marked @IBAction as a hint to the storyboard editor, and taking a single parameter, a UIStoryboardSegue. You can call it unwind: if you like, but the name doesn’t really matter:

@IBAction func unwind(seg:UIStoryboardSegue!) {
    // ...
}

Think of this method as a marker, specifying that the view controller in which it appears can be the destination for an unwind segue. It is, in fact, a little more than a marker: it will also be called when the unwind segue is triggered. But its marker functionality is much more important — so much so that, in many cases, you won’t give this method any code at all. Its presence, and its name, are what matters.

Now you can create an unwind segue. Doing so involves the use of the Exit proxy object that appears in every scene of a storyboard. Control-drag from the view controller you want to go back from, or from something like a button in that view controller’s view, connecting it to the Exit proxy object in the same scene (Figure 6-10). A little HUD appears, listing all the unwind methods known to this storyboard (similar to how action methods are listed in the HUD when you connect a button to its target). Click the name of the unwind method you want. You have now made an unwind segue, bound to that unwind method.

Creating an unwind segue
Figure 6-10. Creating an unwind segue

How an unwind segue works

When the unwind segue is triggered, the following steps are performed:

  1. The source view controller’s shouldPerformSegueWithIdentifier:sender: is called — just as for a normal segue. This is your chance to stop the whole process dead at this point by returning false.
  2. The name of the unwind method to which the unwind segue is bound is only a name. The unwind segue’s source view controller is the view controller that contains it. But its destination view controller is unknown. Therefore, the runtime now starts walking up the view controller hierarchy looking for a destination view controller. Put simply, the first view controller it finds that implements the unwind method will, by default, be the destination view controller.
  3. The source view controller’s prepareForSegue:sender: is called — just as for a normal segue. The two view controllers are now in contact (because the other view controller is the segue’s destinationViewController). This is an opportunity for the source view controller to hand information to the destination view controller before being destroyed! (Thus, an unwind segue is an alternative to a delegate property as a way of putting one view controller into communication with another: see Communication With a Presented View Controller.)
  4. The destination view controller’s unwind method is called. Its parameter is the segue; this segue can be identified through its identifier property. The two view controllers are now in contact again (because the other view controller is the segue’s sourceViewController). It is perfectly reasonable, as I’ve already said, for the unwind method body to be empty; the unwind method’s real purpose is to mark this view controller as the destination view controller.
  5. The segue is performed, destroying the source controller and any intervening view controllers up to (but not including) the destination view controller, in good order.

Now I’ll go back and explain in detail the second step and the last step: how the destination view controller is found, and how the segue is actually performed. This is partly out of sheer interest (they are both devilishly clever), and partly in case you need to customize the process.

Tip

These procedures are both new in iOS 9. Apple has revised unwind segues, sometimes quite dramatically, with each major iOS update since they were introduced in iOS 6; this newest revision is a brilliant simplification and rationalization.

How the destination view controller is found

We begin with how the destination view controller is located. The process is initially one of walking up the view controller hierarchy. What do I mean by “up” the hierarchy? Well, every view controller has either a parentViewController or a presentingViewController, so the next view controller up the hierarchy is that view controller. However, it might also be necessary to walk back down the hierarchy, to a child (at some depth) of one of the parents we encounter. In iOS 8 and before, it was necessary for every parent view controller to know how to find the unwind segue’s destination view controller amongst its children. But in iOS 9, the runtime discovers the view controller hierarchy and, if needed, walks both up and then down the hierarchy for you.

Here’s how it does that:

  1. At each step up the view controller hierarchy, the runtime sends this view controller the following event:

    • allowedChildViewControllersForUnwindingFromSource:

    This view controller’s job is to supply an array of its own direct children. The array can be empty, but it must be an array. To do so, the view controller calls this method:

    • childViewControllerContainingSegueSource:

    This tells the view controller which of its own children is, or is the ultimate parent of, the source view controller. We don’t want to go down that branch of the view hierarchy; that’s the branch we just came up. So this view controller subtracts that view controller from the array of its own child view controllers, and returns the resulting array.

  2. There are two possible kinds of result from the previous step (the value returned from allowedChildViewControllers...):

    There are children
    If the previous step yielded an array with one or more child view controllers in it, the runtime performs step 1 on all of them (until it finds the destination), thus going down the view hierarchy.
    There are no children

    If, on the other hand, the previous step yielded an empty array, the runtime asks this same view controller the following question:

    • canPerformUnwindSegueAction(fromViewController:withSender:)

    The default implementation of this method is simply to call respondsToSelector: on self, asking whether this view controller contains an implementation of the unwind method we’re looking for. The result is a Bool. If it is true, we stop. This is the destination view controller. If it is false, we continue with the search up the view controller hierarchy, finding the next view controller and performing step 1 again.

A moment’s thought will reveal that the recursive application of this algorithm will eventually arrive at an existing view controller instance with an implementation of the unwind method if there is one. Okay, maybe a moment’s thought didn’t reveal that to you, so here’s an actual example. I’ll use the app whose storyboard is pictured in Figure 6-9. Its root view controller is a UITabBarController with two children:

  • The first child is a UINavigationController with a root view controller called FirstViewController, which has a push segue to another view controller called ExtraViewController.
  • The second child is called SecondViewController, which has a modal segue to another view controller called ExtraViewController2.

The unwind method is in FirstViewController, and is called iAmFirst:. The corresponding unwind segue, whose action is "iAmFirst:", is triggered from a button in ExtraViewController2. Assume that the user starts in the tab bar controller’s first view controller, where she triggers the push segue, thus showing ExtraViewController. She then switches to the tab bar controller’s second view controller, where she triggers the modal segue, thus showing ExtraViewController2. All the view controllers pictured in Figure 6-9 now exist simultaneously.

The user now taps the button in ExtraViewController2 and thus triggers the "iAmFirst:" unwind segue. What will happen? First, ExtraViewController2 is sent shouldPerformSegueWithIdentifier: and returns true, permitting the segue to go forward. The runtime now needs to walk the view controller hierarchy and locate the iAmFirst: method. Here’s how it does that:

  1. We start by walking up the view hierarchy. We thus arrive at the original presenter from which ExtraViewController2 was presented, namely SecondViewController. The runtime sends SecondViewController allowedChildViewControllersForUnwindingFromSource:; the childless SecondViewController returns an empty array. So the runtime also asks SecondViewController canPerformUnwindSegueAction:... to find out whether this is the destination; SecondViewController returns false, so we know this is not the destination.
  2. We therefore proceed up the view hierarchy to SecondViewController’s parent, the UITabBarController. The runtime sends the UITabBarController allowedChildViewControllersForUnwindingFromSource:. The UITabBarController has two child view controllers, namely the UINavigationController and SecondViewController — but one of them, SecondViewController, contains the source (as it discovers by calling childViewControllerContainingSegueSource:). Therefore the UITabBarController returns an array containing just one child view controller, namely the UINavigationController.
  3. The runtime has received an array with a child in it; it therefore proceeds down the view hierarchy to that child, the UINavigationController, and asks it the same question: allowedChildViewControllersForUnwindingFromSource:. The navigation controller has two children, namely FirstViewController and ExtraViewController, and neither of them is or contains the source, so it returns an array containing both of them.
  4. The runtime therefore notes down that it has two hierarchy branches to explore, and proceeds to explore them:

    1. The runtime starts with ExtraViewController, asking it allowedChildViewControllersForUnwindingFromSource:. The reply is an empty array, so the runtime asks canPerformUnwindSegueAction:... to find out whether this is the destination — but ExtraViewController replies false.
    2. So much for that branch of the UINavigationController’s children; we’ve reached a dead end. So the runtime proceeds to the other branch, namely FirstViewController. The runtime asks FirstViewController allowedChildViewControllersForUnwindingFromSource:. The reply is an empty array, so the runtime asks canPerformUnwindSegueAction:... to find out whether this is the destination — and FirstViewController replies true. We’ve found the destination view controller!

The destination having been found, the runtime now sends prepareForSegue:sender: to the source, and then calls the destination’s unwind method, iAmFirst:. We are now ready to perform the segue.

How an unwind segue is performed

The way an unwind segue is performed is just as ingenious as how the destination is found. During the walk, the runtime remembers the walk. Thus, it knows where all the presented view controllers are, and it knows where all the parent view controllers are — the view controllers that returned an array containing view controllers from allowedChildViewControllersForUnwindingFromSource:. Thus we have a path of presenting view controllers and parent view controllers between the source and the destination. The runtime then proceeds as follows:

  • For any presented view controllers, the runtime itself calls dismissViewControllerAnimated:completion: on the presenting view controller.
  • For any parent view controllers, the runtime tells each of them, in turn, to unwindForSegue:towardsViewController:.

The second parameter of unwindForSegue:towardsViewController: is the direct child of this parent view controller leading down the branch where the destination lives. This child might or might not be the destination, but that’s no concern of this parent view controller. Its job is merely to get us onto that branch, whatever that may mean for this kind of parent view controller. A moment’s thought will reveal (don’t you wish I’d stop saying that?) that if each parent view controller along the path of parent view controllers does this correctly, we will in fact end up at the destination, releasing in good order all intervening view controllers that need to be released. This procedure, new to iOS 9, is called incremental unwind.

Thus, the unwind procedure for our example runs as follows:

  1. The runtime sends dismissViewControllerAnimated:completion: to the root view controller, namely the UITabBarController. Thus, ExtraViewController2 is destroyed in good order.
  2. The runtime sends unwindForSegue:towardsViewController: to the UITabBarController. The second parameter is the tab bar controller’s first child, the UINavigationController. The UITabBarController therefore changes its selectedViewController to be the UINavigationController.
  3. The runtime sends unwindForSegue:towardsViewController: to the UINavigationController. The second parameter is the FirstViewController. The navigation controller therefore pops its stack down to the FirstViewController. Thus, ExtraViewController is destroyed in good order, and we are back at the FirstViewController — which is exactly what was supposed to happen.

Unwind segue customization

Knowing how an unwind segue works, you can see how to intervene in and customize the process:

  • In a custom view controller that contains an implementation of the unwind method, you might implement can⁠Per⁠form⁠Un⁠wind⁠Segue⁠Ac⁠tion:⁠from⁠View⁠Con⁠trol⁠ler:​with­Sen⁠der: to return false instead of true so that it doesn’t become the destination on this occasion.
  • In a custom parent view controller, you might implement allowedChildViewControllersForUnwindingFromSource:. In all probability, your implementation will consist simply of listing your childViewControllers, calling childViewControllerContainingSegueSource: to find out which of your children is or contains the source, subtracting that child from the array, and returning the array.
  • In a custom parent view controller, you might implement unwindForSegue:towardsViewController:. The second parameter is one of your current children; you will do whatever it means for this parent view controller to make this the currently displayed child.

Warning

Do not override childViewControllerContainingSegueSource:. It knows more than you do; you wouldn’t want to interfere with its operation.

View Controller Lifetime Events

As views come and go, driven by view controllers and the actions of the user, events arrive that give your view controller the opportunity to respond to the various stages of its own existence and the management of its view. By overriding these methods, your UIViewController subclass can perform appropriate tasks at appropriate moments. Here’s a list:

viewDidLoad
The view controller has obtained its view. See the discussion earlier in this chapter of how a view controller gets its view. Recall that this does not mean that the view is in the interface or even that it has been given its correct size.
willTransitionToTraitCollection:withTransitionCoordinator:
viewWillTransitionToSize:withTransitionCoordinator:
traitCollectionDidChange:
The view controller’s view is being resized or the trait environment is changing (or both). See the discussion of resizing events earlier in this chapter. Your implementation of the first two methods should call super.
updateViewConstraints
viewWillLayoutSubviews
viewDidLayoutSubviews
The view is receiving updateConstraints and layoutSubviews events. See Chapter 1, and the discussion of resizing events earlier in this chapter. Your implementation of updateViewConstraints should call super.
willMoveToParentViewController:
didMoveToParentViewController:
The view controller is being added or removed as a child of another view controller. See the discussion of container view controllers earlier in this chapter.
viewWillAppear:
viewDidAppear:
viewWillDisappear:
viewDidDisappear:

The view is being added to or removed from the interface. This includes being supplanted by another view controller’s view or being restored through the removal of another view controller’s view. A view that has appeared (or has not yet disappeared) is in the window; it is part of your app’s active view hierarchy. A view that has disappeared (or has not yet appeared) is not in the window; its window is nil. You should call super in your override of any of these four methods; if you forget to do so, things may go wrong in subtle ways.

To distinguish more precisely why your view is appearing or disappearing, call any of these methods on self:

  • isBeingPresented
  • isBeingDismissed
  • isMovingToParentViewController
  • isMovingFromParentViewController

A good way to get a sense for when these events are useful is to track the sequence in which they normally occur. Take, for example, a UIViewController being pushed onto the stack of a navigation controller. It receives, in this order, the following messages:

  • willMoveToParentViewController:
  • viewWillAppear:
  • updateViewConstraints
  • traitCollectionDidChange:
  • viewWillLayoutSubviews
  • viewDidLayoutSubviews
  • viewDidAppear:
  • didMoveToParentViewController:

(There is then a second round of layout messages.)

When this same UIViewController is popped off the stack of the navigation controller, it receives, in this order, the following messages:

  • willMoveToParentViewController: (with parameter nil)
  • viewWillDisappear:
  • updateViewConstraints
  • viewWillLayoutSubviews
  • viewDidLayoutSubviews
  • viewDidDisappear:
  • didMoveToParentViewController: (with parameter nil)

Disappearance, as I mentioned a moment ago, can happen because another view controller’s view supplants this view controller’s view. For example, consider a UIViewController functioning as the top (and visible) view controller of a navigation controller. When another view controller is pushed on top of it, the first view controller gets these messages:

  • viewWillDisappear:
  • updateViewConstraints
  • viewWillLayoutSubviews
  • viewDidLayoutSubviews
  • viewDidDisappear:
  • didMoveToParentViewController:

The converse is also true. For example, when a view controller is popped from a navigation controller, the view controller that was below it in the stack (the back view controller) receives these events:

  • viewWillAppear:
  • updateViewConstraints
  • viewWillLayoutSubviews
  • viewDidLayoutSubviews
  • viewDidAppear
  • didMoveToParentViewController:

(There is then a second round of layout messages.)

Incoherencies in View Controller Events

Unfortunately, the exact sequence of events and the number of times they will be called for any given view controller transition situation sometimes seems nondeterministic or incoherent. The previous section contains a number of cases in point, and there are others. For example:

  • Sometimes didMoveToParentViewController: arrives without a corresponding willMoveToParentViewController:.
  • Sometimes didMoveToParentViewController: arrives even though this view controller was previously the child of this parent and remains the child of this parent.
  • Sometimes the layout events (updateViewConstraints, viewWillLayoutSubviews, viewDidLayoutSubviews) arrive more than once for the same view controller for the same transition.
  • Sometimes the layout events arrive needlessly, as when the view controller’s view is about to leave the interface and the view controller is about to be destroyed.
  • Sometimes viewWillAppear: or viewWillDisappear: arrives without the corresponding viewDidAppear: or viewDidDisappear:. For example, if an interactive transition animation begins and is cancelled, the cancelled view controller receives viewWillAppear: at the start, without viewDidAppear:, and receives viewWillDisappear: and viewDidDisappear: at the end.

The best advice I can offer is that you should try to structure your code in such a way that incoherencies of this sort don’t matter. For example, if you do something in viewWillAppear: that needs to be cancelled or reversed in viewDidAppear:, you should also cancel or reverse it in viewWillDisappear:.

Appear and Disappear Events

The Appear and Disappear events are particularly appropriate for making sure that a view reflects the model or some form of saved state whenever it appears. Changes to the interface performed in viewDidAppear: or viewWillDisappear: may be visible to the user as they occur! If that’s not what you want, use the other member of the pair. For example, in a certain view containing a long scrollable text, I want the scroll position to be the same when the user returns to this view as it was when the user left it, so I save the scroll position in viewWillDisappear: and restore it in viewWillAppear: (not viewDidAppear:, where the user might see the scroll position jump).

These methods are useful also when something must be true exactly so long as a view is in the interface. For example, a repeating NSTimer that must be running while a view is present can be started in the view controller’s viewDidAppear: and stopped in its viewWillDisappear:. (This architecture also allows you to avoid the retain cycle that could result if you waited to invalidate the timer in a deinit that might otherwise never arrive.)

A view does not disappear if a presented view controller’s view merely covers it rather than supplanting it. For example, a view controller that presents another view controller using the .FormSheet presentation style gets no lifetime events during presentation and dismissal.

A view does not disappear merely because the app is backgrounded and suspended. Once suspended, your app might be killed. So you cannot rely on viewWillDisappear: and viewDidDisappear: alone for saving data that the app will need the next time it launches. If you are to cover every case, you may need to ensure that your data-saving code also runs in response to an application lifetime event such as applicationWillResignActive: or applicationDidEnterBackground: (and see Appendix A for a discussion of the application lifetime events).

Event Forwarding to a Child View Controller

A custom container (parent) view controller, as I explained earlier, must effectively send willMoveToParentViewController: and didMoveToParentViewController: to its children manually.

It must also forward resizing events to its children. This will happen automatically if you call super in your implementation of willTransitionToTraitCollection:... and viewWillTransitionToSize:.... By the same token, if you implement these methods, failure to call super may prevent them from being forwarded correctly to the child view controller.

The Appear and Disappear events are normally passed along automatically. However, you can take charge by implementing this method:

shouldAutomaticallyForwardAppearanceMethods

If you override this method to return false, you are responsible for seeing that these methods on your view controller’s children are called:

  • viewWillAppear:
  • viewDidAppear:
  • viewWillDisappear:
  • viewDidDisappear:

You do not do this by calling these methods directly. The reason is that you have no access to the correct moment for sending them. Instead, you call these two methods on your child view controller:

  • beginAppearanceTransition:animated:; the first parameter is a Bool saying whether this view controller’s view is about to appear (true) or disappear (false)
  • endAppearanceTransition

Here’s what to do if you’ve implemented shouldAutomaticallyForwardAppearanceMethods to return false. There are two main occasions on which your custom container view controller must forward Appear and Disappear events to a child.

First, what happens when your custom container view controller’s own view itself appears or disappears? If it has a child view controller’s view within its own view, it must implement and forward all four Appear and Disappear events to that child. You’ll need an implementation along these lines, for each of the four events:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let child = // whatever
    if child.isViewLoaded() && child.view.superview != nil {
        child.beginAppearanceTransition(true, animated: true)
    }
}
override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    let child = // whatever
    if child.isViewLoaded() && child.view.superview != nil {
        child.endAppearanceTransition()
    }
}

(The implementations for viewDidAppear: and viewDidDisappear: are similar, except that the first argument for beginAppearanceTransition: is false.)

Second, what happens when you swap one view controller’s child for another in your interface? You must not call the UIViewController method transitionFromViewController:toViewController:...! It takes charge of sending the four events to the children itself, and it isn’t going to do so correctly in this situation. Instead, you must perform the transition animation directly. A minimal correct implementation might involve the UIView class method transitionFromView:toView:... (see Chapter 4). Here, you can and should call beginAppearanceTransition: and endAppearanceTransition yourself.

Here’s an example of a parent view controller swapping one child view controller and its view for another, while taking charge of notifying the child view controllers of the appearance and disappearance of their views. I’ve put asterisks to call attention to the additional method calls that forward the Appear and Disappear events to the children (fromvc and tovc):

self.addChildViewController(tovc)
fromvc.willMoveToParentViewController(nil)
fromvc.beginAppearanceTransition(false, animated:true) // *
tovc.beginAppearanceTransition(true, animated:true) // *
UIView.transitionFromView(fromvc.view,
    toView:tovc.view,
    duration:0.4,
    options:.TransitionFlipFromLeft,
    completion:{
        _ in
        tovc.endAppearanceTransition() // *
        fromvc.endAppearanceTransition() // *
        tovc.didMoveToParentViewController(self)
        fromvc.removeFromParentViewController()
})

View Controller Memory Management

Memory is at a premium on a mobile device. Thus you want to minimize your app’s use of memory. Your motivations are partly altruistic and partly selfish. While your app is running, other apps are suspended in the background; you want to keep your memory usage as low as possible so that those other apps have room to remain suspended and the user can readily switch to them from your app. You also want to prevent your own app from being terminated! If your app is backgrounded and suspended while using a lot of memory, it may be terminated in the background when memory runs short. If your app uses an inordinate amount of memory while in the foreground, it may be summarily killed before the user’s very eyes.

One strategy for avoiding using too much memory is to release any memory-hogging objects you’re retaining if they are not needed at this moment. Because a view controller is the basis of so much of your application’s architecture, it is likely to be a place where you’ll concern yourself with releasing unneeded memory.

One of your view controller’s most memory-intensive objects is its view. Fortunately, the iOS runtime manages a view controller’s view’s memory for you. If a view controller’s view is not in the interface, it can be temporarily dispensed with. In such a situation, if memory is getting tight, then even though the view controller itself persists, and even though it retains its actual view, the runtime may release its view’s backing store (the cached bitmap representing the view’s drawn contents). The view will then be redrawn when and if it is to be shown again later.

In addition, if memory runs low, your view controller may be sent this message:

didReceiveMemoryWarning
Sent to a view controller to advise it of a low-memory situation. It is preceded by a call to the app delegate’s applicationDidReceiveMemoryWarning:, together with a UIApplicationDidReceiveMemoryWarningNotification posted to any registered objects. You are invited to respond by releasing any data that you can do without. Do not release data that you can’t readily and quickly recreate! The documentation advises that you should call super.

If you’re going to release data in didReceiveMemoryWarning, you must concern yourself with how you’re going to get it back. A simple and reliable mechanism is lazy loading — a getter that reconstructs or fetches the data if it is nil.

For example, suppose we have a property myBigData which might be a big piece of data. We make this a calculated property, storing the real data in a private property (I’ll call it myBigDataReal). Our calculated property’s setter simply writes through to the private property. In didReceiveMemoryWarning we write myBigData out as a file to disk (Chapter 23) and set myBigData to nil — thus setting myBigDataReal to nil as well, and releasing the big data from memory. The getter for myBigData implements lazy loading: if we try to get myBigData when myBigDataReal is nil, we attempt to fetch the data from disk — and if we succeed, we delete it from disk (to prevent stale data):

private var myBigDataReal : NSData!
var myBigData : NSData! {
    set (newdata) {
        self.myBigDataReal = newdata
    }
    get {
        if myBigDataReal == nil {
            let fm = NSFileManager()
            let f = (NSTemporaryDirectory() as NSString)
                .stringByAppendingPathComponent("myBigData")
            if fm.fileExistsAtPath(f) {
                self.myBigDataReal = NSData(contentsOfFile: f)
                do {
                    try fm.removeItemAtPath(f)
                } catch {
                    print("Couldn't remove temp file")
                }
            }
        }
        return self.myBigDataReal
    }
}
func saveAndReleaseMyBigData() {
    if let myBigData = self.myBigData {
        let f = (NSTemporaryDirectory() as NSString)
            .stringByAppendingPathComponent("myBigData")
        myBigData.writeToFile(f, atomically:false)
        self.myBigData = nil
    }
}
override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    self.saveAndReleaseMyBigData()
}

When your big data can be reconstructed from scratch on demand, you can take advantage of the built-in NSCache class, which is like a dictionary with the ability to clear out its own entries automatically under memory pressure. As in the previous example, a calculated property can be used as a façade:

let cache = NSCache()
var cachedData : NSData {
    let key = // ...
    var data = self.cache.objectForKey(key) as? NSData
    if data != nil {
        return data!
    }
    // ... recreate data here ...
    data = // recreated data
    self.cache.setObject(data!, forKey: key)
    return data!
}

Another built-in class that knows how to clear itself out is NSPurgeableData. It is a subclass of NSMutableData that adopts the NSDiscardableContent protocol (which your own classes can also adopt). NSCache knows how to work with classes that adopt NSDiscardableContent, or you can use such classes independently. To signal that the data should be discarded, send your object discardContentIfPossible. Wrap any access to data in calls to beginContentAccess and endContentAccess; the former returns a Bool to indicate whether the data was accessible. The tricky part is getting those access calls right; when you create an NSPurgeableData, you must send it an unbalanced endContentAccess to make its content discardable:

var purgeable = NSPurgeableData()
var purgeabledata : NSData {
    if self.purgeable.beginContentAccess() && self.purgeable.length > 0 {
        let result = self.purgeable.copy() as! NSData
        self.purgeable.endContentAccess()
        return result
    } else {
        // ... recreate data here ...
        let data = // recreated data
        self.purgeable = NSPurgeableData(data:data)
        self.purgeable.endContentAccess()
        return data
    }
}

(For more about NSCache and NSPurgeableData, see the “Caching and Purgeable Memory” chapter of Apple’s Memory Usage Performance Guidelines.)

At an even lower level, you can store your data on disk (in some reasonable location such the Caches directory) and read it using NSData(contentsOfURL:options:) with an options: argument .DataReadingMappedAlways. This creates a memory-mapped data object, which has the remarkable feature that it isn’t considered to belong to your memory at all; the system has no hesitation in clearing it from RAM, because it is backed through the virtual memory system by the file on disk, and will be read back into memory automatically when you next access it. This is suitable only for large immutable data, because small data runs the risk of fragmenting a virtual memory page.

To test low-memory circumstances artificially, run your app in the Simulator and choose Hardware → Simulate Memory Warning. I don’t believe this has any actual effect on memory, but a memory warning of sufficient severity is sent to your app, so you can see the results of triggering your low-memory response code, including the app delegate’s applicationDidReceiveMemoryWarning: and your view controller’s didReceiveMemoryWarning.

On the device, the equivalent is to call an undocumented method:

UIApplication.sharedApplication().performSelector("_performMemoryWarning")

(Be sure to remove that code when it is no longer needed for testing, as the App Store won’t accept it.)

You will also wish to concern yourself with releasing memory when your app is about to be suspended. If your app has been backgrounded and suspended and the system later discovers it is running short of memory, it will go hunting through the suspended apps, looking for memory hogs that it can kill in order to free up that memory. If the system decides that your suspended app is a memory hog, it isn’t politely going to wake your app and send it a memory warning; it’s just going to terminate your app in its sleep. The time to be concerned about releasing memory, therefore, is before the app is suspended. You’ll probably want your view controller to be registered with the shared application to receive UIApplicationDidEnterBackgroundNotification. The arrival of this notification is an opportunity to release any easily restored memory-hogging objects, such as myBigData in the previous example:

override func viewDidLoad() {
    super.viewDidLoad()
    NSNotificationCenter.defaultCenter().addObserver(
        self, selector: "backgrounding:",
        name: UIApplicationDidEnterBackgroundNotification,
        object: nil)
}
func backgrounding(n:NSNotification) {
    self.saveAndReleaseMyBigData()
}

Tip

A very nice feature of NSCache is that it evicts its objects automatically when your app goes into the background.

Testing how your app’s memory behaves in the background isn’t easy. In a WWDC 2011 video, an interesting technique is demonstrated. The app is run under Instruments on the device, using the virtual memory instrument, and is then backgrounded by pressing the Home button, thus revealing how much memory it voluntarily relinquishes at that time. Then a special memory-hogging app is launched on the device: its interface loads and displays a very large image in a UIImageView. Even though your app is backgrounded and suspended, the virtual memory instrument continues to track its memory usage, and you can see whether further memory is reclaimed under pressure from the demands of the memory-hogging app in the foreground.

State Restoration

When the user leaves your app and then later returns to it, one of two things might have happened in the meantime (see Appendix A):

Your app was suspended
Your app was suspended in the background, and remained suspended while the user did something else. When the user returns to your app, the system simply unfreezes your app, and there it is, looking just as it did when the user left it.
Your app was terminated
Your app was suspended in the background, and then, as the user worked with other apps, a moment came where the system decided it needed the resources (such as memory) being held by your suspended app. Therefore it terminated your app. When the user returns to your app, the app launches from scratch.

The user, however, doesn’t know the difference between those two things, so why should the app behave differently some of the time? Ideally, it should always feel to the user as if the app is being resumed from where it left off the last time it was in the foreground, even if in fact the app was terminated while suspended in the background. Otherwise, as the WWDC 2013 video on this topic puts it, the user will feel that the app has “lost my place.”

That’s where state restoration comes in. Your app has a state at every moment: some view controller’s view is occupying the screen, and views within it are displaying certain values (for example, a certain switch is set to On, or a certain table view is scrolled to a certain position). The idea of state restoration is to save that information when the app goes into the background, and use it to make all those things true again if the app is subsequently launched from scratch.

iOS provides a general solution to the problem of state restoration. This solution is centered around view controllers, which makes sense, since view controllers are the heart of the problem. What is the user’s “place” in the app, which we don’t want to “lose”? It’s the chain of view controllers that got us to where we were when the app was backgrounded, along with the configuration of each one. The goal of state restoration must therefore be to reconstruct all existing view controllers, initializing each one into the state it previously had.

Note that state, in this sense, is neither user defaults nor data. If something is a preference, store it in NSUserDefaults. If something is data, keep it in a file (Chapter 23). Don’t misuse the state saving and restoration mechanism for such things. The reason for this is not only conceptual; it’s also because saved state can be lost. You don’t want to commit anything to the state restoration mechanism if it would be a disaster to have lost it the next time the app launches.

For example, suppose the user kills your app outright by double-clicking the Home button to show the app switcher interface and flicking your app’s snapshot upward; the system will throw away its state. Similarly, if your app crashes, the system will throw away its state. In both cases, the system assumes that something went wrong, and doesn’t want to launch your app into what might be a troublesome saved state. Instead, your app will launch cleanly, from the beginning. There’s no problem for the user, barring a mild inconvenience — as long as the only thing that gets thrown away is state.

How to Test State Restoration

To test whether your app is saving and restoring state as you expect:

  1. Run the app as usual, in the Simulator or on a device.
  2. At some point, in the Simulator or on the device, click the Home button (Hardware → Home in the Simulator). This causes the app to be suspended in good order, and state is saved.
  3. Now, back in Xcode, stop the running project (Product → Stop).
  4. Run the project again. If there is saved state, it is restored.

(To test the app’s behavior from a truly cold start, delete it from the Simulator or device. You might need to do this after changing something about the underlying save-and-restore model.)

Apple also provides (at http://developer.apple.com/downloads) some debugging tools:

restorationArchiveTool
A command-line tool letting you examine a saved state archive in textual format. The archive is in a folder called Saved Application State in your app’s sandboxed Library. See Chapter 23 for more about the app’s sandbox, and how to copy it to your computer from a device.
StateRestorationDebugLogging.mobileconfig
A configuration profile. When installed on a device, it causes the console to dump information as state saving and restoration proceeds.
StateRestorationDeveloperMode.mobileconfig
A configuration profile. When installed on a device, it prevents the state archive from being jettisoned after unexpected termination of the app (a crash, or manual termination through the app switcher interface). This can allow you to test state restoration a bit more conveniently.

To install a .mobileconfig file on a device, the simplest approach is to email it to yourself on the device and tap the file in the Mail message. You can subsequently delete the file, if desired, through the Settings app.

Participating in State Restoration

Built-in state restoration is an opt-in technology: it operates only if you explicitly tell the system that you want to participate in it. To do so, you take three basic steps:

Implement app delegate methods

The app delegate must implement these methods to return true:

  • application:shouldSaveApplicationState:
  • application:shouldRestoreApplicationState:

(Naturally, your code can instead return false to prevent state from being saved or restored on some particular occasion.)

Implement application:willFinishLaunchingWithOptions:

Although it is very early, application:didFinishLaunchingWithOptions: is too late for state restoration. Your app needs its basic interface before state restoration begins. The solution is to use a different app delegate method, application:willFinishLaunchingWithOptions:.

Typically, you can just change “did” to “will” in the name of this method, keeping your existing code unchanged. However, your implementation must call makeKeyAndVisible explicitly on the window if the existing code doesn’t already call it! Otherwise, the interface doesn’t come into existence soon enough for restoration to happen during launch.

Provide restoration IDs

Both UIViewController and UIView have a restorationIdentifier property, which is a string. Setting this string to a non-nil value is your signal to the system that you want this view controller (or view) to participate in state restoration. If a view controller’s restorationIdentifier is nil, neither it nor any subsequent view controllers down the chain will be saved or restored. (A nice feature of this architecture is that it lets you participate partially in state restoration, omitting some view controllers by not assigning them a restoration identifier.)

You can set the restorationIdentifier manually, in code; typically you’ll do that early in a view controller’s lifetime. If a view controller or view is instantiated from a nib, you’ll want to set the restoration identifier in the nib editor; the Identity inspector has a Restoration ID field for this purpose. (If you’re using a storyboard, it’s a good idea, in general, to make a view controller’s restoration ID in the storyboard the same as its storyboard ID, the string used to identify the view controller in a call to instantiateViewControllerWithIdentifier:; in fact, it’s such a good idea that the storyboard editor provides a checkbox, “Use Storyboard ID,” that makes the one value automatically the same as the other.)

In the case of a simple storyboard-based app, where each needed view controller instance can be reconstructed directly from the storyboard, those steps alone can be sufficient to bring state restoration to life, operating correctly at the view controller level. Let’s test it. Start with a storyboard-based app with the following architecture (Figure 6-11):

  • A navigation controller.
  • Its root view controller, connected by a relationship from the navigation controller. Call its class RootViewController.

    • A presented view controller, connected by a modal segue from a Present button in the root view controller’s view. Call its class PresentedViewController. Its view contains a Dismiss button.
  • A second view controller, connected by a push segue from a Push bar button item in the root view controller’s navigation item. Call its class SecondViewController.

    • The very same presented view controller (PresentedViewController), also connected by a modal segue from a Present button in the second view controller’s view.
Architecture of an app for testing state restoration
Figure 6-11. Architecture of an app for testing state restoration

This storyboard-based app runs perfectly with just about no code at all; all we need is an empty implementation of an unwind method in RootViewController and SecondViewController so that we can create an unwind segue from the PresentedViewController Dismiss button.

We will now make this app implement state restoration:

  1. Change the name of application:didFinishLaunchingWithOptions: in the app delegate to application:willFinishLaunchingWithOptions:, and insert this line of code if it isn’t already present:

    self.window?.makeKeyAndVisible()
  2. Implement application:shouldSaveApplicationState: and app⁠lic⁠ation:​should⁠Res⁠tore⁠App⁠lica⁠tion⁠State: in the app delegate to return true.
  3. Give all four view controller instances in the storyboard restoration IDs: let’s call them "nav", "root", "second", and "presented".

That’s all! The app now saves and restores state. When we run the app, navigate to another view controller, quit, and later relaunch, the app appears in the same view controller it was in when we quit.

Tip

Before calling makeKeyAndVisible, it may also be useful to assign the window a restoration identifier: self.window?.restorationIdentifier = "window". This might not make any detectable difference, but in some cases it can help restore size class information.

Restoration ID, Identifier Path, and Restoration Class

Having everything done for us by the storyboard reveals nothing about what’s really happening. To learn more, let’s rewrite the example without a storyboard. Throw away the storyboard (and delete the Main Storyboard entry from the Info.plist) and implement the same architecture using code alone:

// AppDelegate.swift:
func application(application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?)
    -> Bool {
        self.window = UIWindow()
        let rvc = RootViewController()
        let nav = UINavigationController(rootViewController:rvc)
        self.window!.rootViewController = nav
        self.window!.backgroundColor = UIColor.whiteColor()
        self.window!.makeKeyAndVisible()
        return true
}

// RootViewController.swift:
override func viewDidLoad() {
    super.viewDidLoad()
    // ... color view background, create buttons ...
}
func doPresent(sender:AnyObject?) {
    let pvc = PresentedViewController()
    self.presentViewController(pvc, animated:true, completion:nil)
}
func doPush(sender:AnyObject?) {
    let svc = SecondViewController()
    self.navigationController!.pushViewController(svc, animated:true)
}

// SecondViewController.swift:
override func viewDidLoad() {
    super.viewDidLoad()
    // ... color view background, create button ...
}
func doPresent(sender:AnyObject?) {
    let pvc = PresentedViewController()
    self.presentViewController(pvc, animated:true, completion:nil)
}

// PresentedViewController.m:
override func viewDidLoad() {
    super.viewDidLoad()
    // ... color view background, create button ...
}
func doDismiss(sender:AnyObject?) {
    self.dismissViewControllerAnimated(true, completion: nil)
}

That’s a working app. Now let’s start adding state restoration, just as before:

  1. Change the name of application:didFinishLaunchingWithOptions: in the app delegate to application:willFinishLaunchingWithOptions:.
  2. Implement application:shouldSaveApplicationState: and application:​sho⁠uld⁠Re⁠store⁠App⁠li⁠ca⁠tion⁠State: in the app delegate to return true.
  3. Give all four view controller instances restoration IDs: let’s call them "nav", "root", "second", and "presented". We’ll have to do this in code. We’re creating each view controller instance manually, so we may as well assign its restorationIdentifier in the next line, like this:

    let rvc = RootViewController()
    rvc.restorationIdentifier = "root"
    let nav = UINavigationController(rootViewController:rvc)
    nav.restorationIdentifier = "nav"

    And so on.

Run the app. We are not getting state restoration. Why not?

The reason is that the restorationIdentifier alone is not sufficient to tell the state restoration mechanism what to do as the app launches. The restoration mechanism knows the chain of view controller classes that needs to be generated, but it is up to us to generate the instances of those classes. (Our storyboard-based example didn’t exhibit this problem, because the storyboard itself was the source of the instances.) To do that, we need to know about the identifier path and the restoration class.

The restorationIdentifier serves as a guide during restoration as to what view controller is needed at each point in the view controller hierarchy. Any particular view controller instance, given its position in the view controller hierarchy, is uniquely identified by the sequence of restorationIdentifier values of all the view controllers (including itself) in the chain that leads to it. Those restorationIdentifier values, taken together and in sequence, constitute the identifier path for any given view controller instance.

Each identifier path is, in fact, merely an array of strings. In effect, the identifier paths are like a trail of breadcrumbs that you left behind as you created each view controller while the app was running, and that will now be used to identify each view controller again as the app launches.

For example, if we launch the app and press the Push button and then the Present button, then all four view controllers have been instantiated; those instances are identified as:

  • ["nav"]
  • ["nav", "root"]
  • ["nav", "second"]
  • ["nav", "presented"] (because the navigation controller is the actual presenting view controller)

Observe that a view controller’s identifier path is not a record of the full story of how we got here. It’s just an identifier! The state-saving mechanism also saves a relational tree of identifiers. For example, if the app is suspended in the current situation, then the state-saving mechanism will record the true state of affairs, namely that the root view controller has two children and a presented view controller, along with their identifiers.

Now consider what the state restoration mechanism needs to do when the app has been suspended and killed, and comes back to life, from the situation I just described. We need to restore four view controllers; we know their identifiers and mutual relationships. State restoration doesn’t start until after application:willFinishLaunchingWithOptions:. So when the state restoration mechanism starts examining the situation, it discovers that the ["nav"] and ["nav", "root"] view controller instances have already been created! However, the view controller instances for ["nav", "second"] and ["nav", "presented"] must also be created now. The state restoration mechanism doesn’t know how to do that — so it’s going to ask your code for the instances.

But what code should it ask? One way of specifying this is for you to provide a restoration class for each view controller instance that is not restored by the time application:willFinishLaunchingWithOptions: returns. Here’s how you do that:

  1. Give the view controller a restorationClass. Typically, this will be the view controller’s own class, or the class of the view controller responsible for creating this view controller instance.
  2. Implement the class method viewControllerWithRestorationIdentifierPath:coder: on the class named by each view controller’s restorationClass property, returning a view controller instance as specified by the identifier path. Very often, the implementation will be to instantiate the view controller directly and return that instance.
  3. Specify formally that each class named as a restorationClass implements the UIViewControllerRestoration protocol. (If you omit this step, you’ll get a helpful warning message at runtime: “Restoration class for view controller does not conform to UIViewControllerRestoration protocol.”)

Let’s make our PresentedViewController and SecondViewController instances restorable. I’ll start with PresentedViewController. Our app can have two PresentedViewController instances (though not simultaneously) — the one created by RootViewController, and the one created by SecondViewController. Let’s start with the one created by RootViewController.

Since RootViewController creates and configures a PresentedViewController instance, it can reasonably act also as the restoration class for that instance. In its implementation of viewControllerWithRestorationIdentifierPath:coder:, RootViewController should then create and configure a PresentedViewController instance exactly as it was doing before we added state restoration to our app — except for putting it into the view controller hierarchy! The state restoration mechanism itself, remember, is responsible for assembling the view controller hierarchy; our job is merely to supply any needed view controller instances.

So RootViewController now must adopt UIViewControllerRestoration, and will contain this code:

func doPresent(sender:AnyObject?) {
    let pvc = PresentedViewController()
    pvc.restorationIdentifier = "presented"
    pvc.restorationClass = self.dynamicType // *
    self.presentViewController(pvc, animated:true, completion:nil)
}
class func viewControllerWithRestorationIdentifierPath(ip: [AnyObject],
    coder: NSCoder) -> UIViewController? {
        var vc : UIViewController? = nil
        let last = ip.last as! String
        switch last {
        case "presented":
            let pvc = PresentedViewController()
            pvc.restorationIdentifier = "presented"
            pvc.restorationClass = self
            vc = pvc
        default: break
        }
        return vc
}

You can see what I mean when I say that the restoration class must do exactly what it was doing before state restoration was added. Clearly this situation has led to some annoying code duplication, so let’s factor out the common code. In doing so, we must bear in mind that doPresent: is an instance method, whereas viewControllerWithRestorationIdentifierPath:coder: is a class method; our factored-out code must therefore be a class method, so that they can both call it:

class func makePresentedViewController () -> UIViewController {
    let pvc = PresentedViewController()
    pvc.restorationIdentifier = "presented"
    pvc.restorationClass = self
    return pvc
}
func doPresent(sender:AnyObject?) {
    let pvc = self.dynamicType.makePresentedViewController()
    self.presentViewController(pvc, animated:true, completion:nil)
}
class func viewControllerWithRestorationIdentifierPath(ip: [AnyObject],
    coder: NSCoder) -> UIViewController? {
        var vc : UIViewController? = nil
        let last = ip.last as! String
        switch last {
        case "presented":
            vc = self.makePresentedViewController()
        default: break
        }
        return vc
}

The structure of our viewControllerWithRestorationIdentifierPath:coder: is typical. We test the identifier path — usually, it’s sufficient to examine its last element — and return the corresponding view controller; ultimately, we are also prepared to return nil, in case we are called with an identifier path we can’t interpret. We can also return nil deliberately, to tell the restoration mechanism, “Go no further; don’t restore the view controller you’re asking for here, or any view controller further down the same path.”

Continuing in the same vein, we expand RootViewController still further to make it also the restoration class for SecondViewController, and SecondViewController can make itself the restoration class for the PresentedViewController instance that it creates. There’s no conflict in the notion that both RootViewController and SecondViewController can fulfill the role of PresentedViewController restoration class, as we’re talking about two different PresentedViewController instances. The app now performs state saving and restoration correctly! (The details are left as an exercise for the reader.)

I said earlier that the state restoration mechanism can ask your code for needed instances in two ways. The second way is that you implement this method in your app delegate:

  • application:viewControllerWithRestorationIdentifierPath:coder:

If you implement this method, it will be called for every view controller that doesn’t have a restoration class. This works in a storyboard-based app, and thus is a chance for you to intervene and prevent the restoration of a particular view controller on a particular occasion (by returning nil). Be prepared to receive identifier paths for an existing view controller! If that happens, return the existing view controller — don’t make a new one.

For example, if we were to implement application:viewControllerWithRestorationIdentifierPath:coder: in the example app I’ve been describing, it would be called for ["nav"] and for ["nav", "root"], because those view controllers have no restoration class. But we needn’t, and we mustn’t, create a new view controller; those view controller instances have already been created, and we must return those existing instances.

Restoring View Controller State

I have explained how the state restoration mechanism creates a view controller and places it into the view controller hierarchy. But at that point, the work of restoration is only half done. What about the state of that view controller?

A newly restored view controller probably won’t yet have the data and property values it was holding at the time the app was terminated. The history of the creation and configuration of this view controller is not magically recapitulated during restoration. If the view controller comes from a storyboard, then any settings in its Attributes inspector are obeyed, but the segue that generated the view controller in the first place is never run, so the previous view controller’s prepareForSegue:sender: is never called, and the previous view controller never gets to hand this view controller any data. If the view controller is created by a restoration class, it may have been given some initial configuration, but this very likely falls short of the full state that the view controller was holding when the app was terminated. Any additional communication between one view controller and another to hand it data will be missing from the process. Indeed, since the history of the app during its previous run is not recapitulated, there will be no data to hand over in the first place.

It is up to each view controller, therefore, to restore its own state when it itself is restored. And in order to do that, it must previously save its own state when the app is backgrounded. The state saving and restoration mechanism provides a way of helping your view controllers do this, through the use of a coder (an NSCoder object). Think of this coder as a box in which the view controller is invited to place its valuables for safekeeping, and from which it can retrieve them later. Each of these valuables needs to be identified, so it is tagged with a key (an arbitrary string) when it is placed into the box, and is then later retrieved by using the same key, much as in a dictionary.

Anyone who has anything to save at the time it is handed a coder can do so by sending the coder an appropriate encode message with a key, such as encodeFloat:forKey: or encodeObject:forKey:. If an object’s class doesn’t adopt the NSCoding protocol, you may have to archive it to an NSData object before you can encode it. However, views and view controllers can be handled by the coder directly, because they are treated as references. Whatever was saved in the coder can later be extracted by sending the coder the reverse operation using the same key, such as decodeFloatForKey: or decodeObjectForKey:.

The keys do not have to be unique across the entire app; they only need to be unique for a particular view controller. Each object that is handed a coder is handed its own personal coder. It is handed this coder at state saving time, and it is handed the same coder (that is, a coder with the same archived objects and keys) at state restoration time.

Here’s the sequence of events involving coders:

When state is saved

When it’s time to save state (as the app is about to be backgrounded), the state saving mechanism provides coders as follows:

  1. The app delegate is sent application:shouldSaveApplicationState:. The coder is the second parameter.
  2. The app delegate is sent application:willEncodeRestorableStateWithCoder:. This is the same coder as in the previous step, because this is the same object (the app delegate).
  3. Each view controller down the chain, starting at the root view controller, is sent encodeRestorableStateWithCoder: if it implements it. The implementation should call super. Each view controller gets its own coder.
When state is restored

When the app is launched, if state is to be restored, the state restoration mechanism provides coders as follows:

  1. The app delegate is sent application:shouldRestoreApplicationState:. The coder (the one belonging to the app delegate) is the second parameter.
  2. As each view controller down the chain is to be created, one of these methods is called (as I’ve already explained):

    • The restoration class’s viewControllerWithRestorationIdentifierPath:coder:, if the view controller has a restoration class.
    • The app delegate’s application:viewControllerWithRestorationIden­tifierPath:coder:, if implemented.

    The coder is the one appropriate to the view controller that’s to be created.

  3. Each view controller down the chain, starting at the root view controller, is sent decodeRestorableStateWithCoder: if it implements it. The implementation should call super. The coder is the one appropriate to this view controller.
  4. The app delegate is sent application:didDecodeRestorableStateWithCoder:. The coder is the same one sent to application:shouldRestoreApplicationState: (the one belonging to the app delegate).

The UIStateRestoration.h header file describes five built-in keys that are available from every coder during restoration:

UIStateRestorationViewControllerStoryboardKey
A reference to the storyboard from which this view controller came, if any.
UIApplicationStateRestorationBundleVersionKey
Your Info.plist CFBundleVersion string at the time of state saving.
UIApplicationStateRestorationUserInterfaceIdiomKey

An NSNumber wrapping a UIUserInterfaceIdiom value, either .Phone or .Pad, telling what kind of device we were running on when state saving happened. You can extract this information as follows:

if let idiomraw = coder.decodeObjectForKey(
    UIApplicationStateRestorationUserInterfaceIdiomKey) as? Int {
        if let idiom = UIUserInterfaceIdiom(rawValue:idiomraw) {
            if idiom == .Phone {
                // ...
            }
        }
}
UIApplicationStateRestorationTimestampKey
An NSDate telling when state saving happened.
UIApplicationStateRestorationSystemVersionKey
A NSString telling the system version under which state saving happened.

One purpose of these keys is to allow your app to opt out of state restoration, wholly or in part, because the archive is too old, was saved on the wrong kind of device (and presumably migrated to this one by backup and restore), and so forth.

A typical implementation of encodeRestorableStateWithCoder: and decodeRestorableStateWithCoder: will concern itself with properties and interface views. decodeRestorableStateWithCoder: is guaranteed to be called after viewDidLoad, so you know that viewDidLoad won’t overwrite any direct changes to the interface performed in decodeRestorableStateWithCoder:.

To illustrate, I’ll add state saving and restoration to my earlier UIPageViewController example, the one that displays a Pep Boy on each page. Recall how that example is architected. The project has no storyboard. The code defines just two classes, the app delegate and the Pep view controller. The app delegate creates a UIPageViewController and makes it the window’s root view controller, and makes itself the page view controller’s data source. The page view controller’s data source methods create and supply an appropriate Pep instance whenever a page is needed for the page view controller, along these lines:

// ... work out index of new name ...
return Pep(pepBoy: self.pep[ix])

The challenge is to restore the Pep object displayed in the page view controller as the app launches. One solution involves recognizing that a Pep object is completely configured once created, and it is created just by handing it the name of a Pep Boy in its designated initializer, which becomes its boy property. Thus we can mediate between a Pep object and a mere string, and all we really need to save and restore is that string.

All the additional work, therefore, can be performed in the app delegate. As usual, we change “did” to “will” so that we are now implementing application:willFinishLaunchingWithOptions:, and we implement application:shouldSaveApplicationState: and application:shouldRestoreApplicationState: to return true. Now we save and restore the current Pep Boy name in the app delegate’s Encode and Decode methods:

func application(application: UIApplication,
    willEncodeRestorableStateWithCoder coder: NSCoder) {
        let pvc = self.window!.rootViewController as! UIPageViewController
        let boy = (pvc.viewControllers![0] as! Pep).boy
        coder.encodeObject(boy, forKey:"boy")
}
func application(application: UIApplication,
    didDecodeRestorableStateWithCoder coder: NSCoder) {
        let boy: AnyObject? = coder.decodeObjectForKey("boy")
        if let boy = boy as? String {
            let pvc = self.window!.rootViewController as! UIPageViewController
            let pep = Pep(pepBoy: boy)
            pvc.setViewControllers(
                [pep], direction: .Forward, animated: false, completion: nil)
        }
}

A second, more general solution is to make our Pep view controller class itself capable of saving and restoration. This means that every view controller down the chain from the root view controller to our Pep view controller must have a restoration identifier. In our simple app, there’s just one such view controller, the UIPageViewController; the app delegate can assign it a restoration ID when it creates it:

let pvc = UIPageViewController(transitionStyle: .Scroll,
    navigationOrientation: .Horizontal, options: nil)
pvc.restorationIdentifier = "pvc" // *

We’ll have a Pep object assign itself a restoration ID in its own designated initializer. The Pep object will also need a restoration class; as I mentioned earlier, this can perfectly well be the Pep class itself, and that seems most appropriate here:

required init(pepBoy boy:String) {
    self.boy = boy
    super.init(nibName: "Pep", bundle: nil)
    self.restorationIdentifier = "pep" // *
    self.restorationClass = self.dynamicType // *
}

The only state that a Pep object needs to save is its boy string. The coder in which that boy value is saved will come back to us in Pep’s viewControllerWithRestorationIdentifierPath:coder:, so we can use it to create the new Pep object by calling the designated initializer, thus avoiding code duplication:

override func encodeRestorableStateWithCoder(coder: NSCoder) {
    super.encodeRestorableStateWithCoder(coder)
    coder.encodeObject(self.boy, forKey:"boy")
}
class func viewControllerWithRestorationIdentifierPath(
    ip: [AnyObject], coder: NSCoder) -> UIViewController? {
        let boy = coder.decodeObjectForKey("boy") as! String
        return self.init(pepBoy: boy)
}

(Swift won’t permit a class to instantiate itself through an initializer on self unless we guarantee that any subclass implements that initializer; our marking of init(pepBoy:) as required constitutes just such a guarantee.)

Now comes a surprise. We run the app and test it, and we find that we’re not getting saving and restoration of our Pep object. It isn’t being archived; its encodeRestorableStateWithCoder: isn’t even being called! The reason is that the state saving mechanism doesn’t work automatically for a UIPageViewController and its children (or for a custom container view controller and its children, for that matter). It is up to us to see to it that the current Pep object is archived.

To do so, we can archive and unarchive the current Pep object in an implementation of encodeRestorableStateWithCoder: and decodeRestorableStateWithCoder: that is being called. For our app, that would have to be in the app delegate. The code we’ve written so far has all been necessary to make the current Pep object archivable and restorable; now the app delegate will make sure that it is archived and restored:

func application(application: UIApplication,
    willEncodeRestorableStateWithCoder coder: NSCoder) {
        let pvc = self.window!.rootViewController as! UIPageViewController
        let pep = pvc.viewControllers![0] as! Pep
        coder.encodeObject(pep, forKey:"pep")
}
func application(application: UIApplication,
    didDecodeRestorableStateWithCoder coder: NSCoder) {
        let pep : AnyObject? = coder.decodeObjectForKey("pep")
        if let pep = pep as? Pep {
            let pvc = self.window!.rootViewController as! UIPageViewController
            pvc.setViewControllers(
                [pep], direction: .Forward, animated: false, completion: nil)
        }
}

This solution may seem rather heavyweight, but it isn’t. We’re not really archiving an entire Pep instance; it’s just a reference. The actual Pep instance is the one created by viewControllerWithRestorationIdentifierPath:coder:.

Restoration Order of Operations

When you implement state saving and restoration for a view controller, the view controller ends up with two different ways of being configured. One way involves the view controller lifetime events I discussed earlier (View Controller Lifetime Events). The other involves the state restoration events I’ve been discussing here. You want your view controller to be correctly configured no matter whether this view controller is undergoing state restoration or not.

Before state saving and restoration, you were probably configuring your view controller, at least in part, in viewWillAppear: and viewDidAppear:. With state saving and restoration added to the picture, you may also be receiving decodeRestorableStateWithCoder:. If you configure your view controller here, will you be overriding what happens in viewWillAppear: and viewDidAppear:, or will they come along later and override what you do in decodeRestorableStateWithCoder:?

The unfortunate fact is that you don’t know. For viewWillAppear: and viewDidAppear:, in particular, the only thing you do know during state restoration is that you’ll get both of them for the top view controller (the one whose view actually appears). You don’t know when they will arrive; it might be before or after decodeRestorableStateWithCoder:. For other view controllers, you don’t even know whether viewDidAppear: will arrive: it might well never arrive, even if viewWillAppear: arrives. This is another of those view controller lifetime event incoherencies I complained about earlier in this chapter.

However, there’s another view controller event I haven’t mentioned yet: applicationFinishedRestoringState. If you implement this method in a view controller subclass, it will be called if and only if we’re doing state restoration, at a time when all view controllers have already been sent decodeRestorableStateWithCoder:.

Thus, the known order of events during state restoration is like this:

  1. application:shouldRestoreApplicationState:
  2. application:viewControllerWithRestorationIdentifierPath:coder:
  3. viewControllerWithRestorationIdentifierPath:coder:, in order down the chain
  4. viewDidLoad, in order down the chain; possibly interleaved with the foregoing
  5. decodeRestorableStateWithCoder:, in order down the chain
  6. application:didDecodeRestorableStateWithCoder:
  7. applicationFinishedRestoringState, in order down the chain

You still don’t know when viewWillAppear: and viewDidAppear: will arrive, or whether viewDidAppear: will arrive at all. But in applicationFinishedRestoringState you can reliably finish configuring your view controller and your interface.

A typical situation is that you will want to update your interface after all properties have been set. So you’ll factor out your interface-updating code into a single method. Now there are two possibilities, and they are both handled coherently:

We’re not restoring state
Properties will be set through initialization and configuration, and then viewWillAppear: calls your interface-updating method.
We are restoring state
Properties will be set by decodeRestorableStateWithCoder:, and then applicationFinishedRestoringState calls your interface-updating method.

There is still some indeterminacy as to what’s going to happen, but the interface-updating method can mediate that indeterminacy by checking for two things that can go wrong:

It is called too soon
The interface-updating method should check to see that the properties have in fact been set; if not, it should just return. It will be called again when the properties have been set.
It is called unnecessarily
The interface-updating method might run twice in quick succession with the same set of properties. This is not a disaster, but if you don’t like it, you can prevent it by comparing the properties to the interface and return if the interface has already been configured with these properties.

Tip

If your app has additional state restoration work to do on a background thread (Chapter 25), the documentation says you should call UIApplication’s extendStateRestoration as you begin and completeStateRestoration when you’ve finished. The idea is that if you don’t call completeStateRestoration, the system can assume that something has gone very wrong and will throw away the saved state information in case it is faulty.

Restoration of Other Objects

A view will participate in automatic saving and restoration of state if its view controller does, and if it itself has a restoration identifier. Some built-in UIView subclasses also have built-in restoration abilities. For example, a scroll view that participates in state saving and restoration will automatically return to the point to which it was scrolled previously. You should consult the documentation on each UIView subclass to see whether it participates usefully in state saving and restoration, and I’ll mention a few significant cases when we come to discuss those views in later chapters.

In addition, an arbitrary object can be made to participate in automatic saving and restoration of state. There are three requirements for such an object:

  • The object’s class must adopt the UIStateRestoring protocol. This declares three optional methods:

    • encodeRestorableStateWithCoder:
    • decodeRestorableStateWithCoder:
    • applicationFinishedRestoringState
  • When the object is created, someone must register it with the runtime by calling this UIApplication class method:

    • registerObjectForStateRestoration:restorationIdentifier:
  • Someone who participates in state saving and restoration, such as a view controller, must make the archive aware of this object by storing a reference to it in the archive (typically in encodeRestorableStateWithCoder:) — much as we did with the Pep object earlier.

So, for example, here’s an NSObject subclass Thing with a word property, that participates in state saving and restoration:

class Thing : NSObject, UIStateRestoring {
    var word = ""
    func encodeRestorableStateWithCoder(coder: NSCoder) {
        coder.encodeObject(self.word, forKey:"word")
    }
    func decodeRestorableStateWithCoder(coder: NSCoder) {
        self.word = coder.decodeObjectForKey("word") as! String
    }
    func applicationFinishedRestoringState() {
        // not used
    }
}

And here’s a view controller with a Thing property (self.thing):

class func makeThing () -> Thing {
    let thing = Thing()
    UIApplication.registerObjectForStateRestoration(
        thing, restorationIdentifier: "thing")
    return thing
}
override func awakeFromNib() {
    super.awakeFromNib()
    self.thing = self.dynamicType.makeThing()
}
override func encodeRestorableStateWithCoder(coder: NSCoder) {
    super.encodeRestorableStateWithCoder(coder)
    coder.encodeObject(self.thing, forKey: "mything") // important!
}

That last line is crucial; it introduces our Thing object to the archive and brings its UIStateRestoring methods to life.

There is an optional objectRestorationClass property of the restorable object, and an objectWithRestorationIdentifierPath:coder: method that the designated class must implement. But our object is restorable even without an objectRestorationClass! Presumably, just calling registerObjectForStateRestoration:restorationIdentifier: sufficiently identifies this object to the runtime. If you do want to assign an objectRestorationClass, you’ll have to declare it:

var objectRestorationClass: UIObjectRestoration.Type?

The class in question should adopt the UIObjectRestoration protocol; its objectWithRestorationIdentifierPath:coder: will then be called, and can return the restorable object, by creating it or pointing to it. Alternatively, it can return nil to prevent restoration.

Another optional property of the restorable object is restorationParent. Again, if you want to assign to it, you’ll have to declare it:

var restorationParent: UIStateRestoring?

The parent should adopt the UIStateRestoring protocol. The purpose of the parent is to give the restorable object an identifier path. For example, if we have a chain of view controllers with a path ["nav", "second"], then if that last view controller is the restorationParent of our Thing object, the Thing object’s identifier path in objectWithRestorationIdentifierPath:coder: will be ["nav", "second", "thing"], rather than simply ["thing"]. This is useful if we are worried that ["thing"] alone will not uniquely identify this object.

Snapshot Suppression

When your app is backgrounded, the system takes a snapshot of your interface. It is used in the app switcher interface, and as a launch image when your app is resumed. But what happens if your app is killed in the background and relaunched?

If your app isn’t participating in state restoration, then its default launch image is used. This makes sense, because your app is starting from scratch. But if your app is participating in state restoration, then the snapshot is used as a launch image. This makes sense, too, because the interface that was showing when the app was backgrounded is presumably the very interface your state restoration process is about to restore.

However, you might decide, while saving state, that there is reason not to use the system’s snapshot when relaunching. (Perhaps there is something in your interface that would be inappropriate to display when the app is subsequently launched.) In that case, you can call the UIApplication instance method ignoreSnapshotOnNextApplicationLaunch. When the app launches with state restoration, the user will see your app’s default launch image, followed by a change to the restored interface. They may not match, but at least there is a nice cross-dissolve between them.

By the same token, if the view controller whose view was showing at state saving time is not restorable (it has no restoration ID), then if the app is killed in the background and subsequently launches with state restoration, the restoration mechanism knows that the snapshot taken at background time doesn’t match the interface we’re about to restore to, so the user will initially see your app’s default launch image.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.39.252