Using View Controllers in Your App

Whether you are working with view controllers provided by iOS, or with custom controllers you’ve created to show your app’s content, you use a similar set of techniques to actually work with the view controllers.

The most common technique for working with view controllers is to place them inside a storyboard. Placing view controllers in a storyboard allows you to directly establish relationships between the view controllers in your app without writing code. You can see the flow of control—from controllers created when your app first launches, to controllers that are instantiated in response to a user’s actions. iOS manages most of this process by instantiating these view controllers only when the app needs them.

Sometimes you may need to create a view controller by allocating and initializing it programmatically. When working with view controllers directly, you must write code that instantiates the view controller, configures it, and displays it.

Working with View Controllers in Storyboards

Figure 2-1 shows an example of a storyboard. This storyboard includes view controllers, associated views, and connection arrows that establish relationships between the view controllers. In effect, this storyboard tells a story, starting with one scene and later showing others in response to a user’s actions.

Figure 2-1  A storyboard holds a set of view controllers and associated objects

A storyboard may designate one view controller to be the initial view controller. If a storyboard represents a specific workflow through part of your UI, the initial view controller represents the first scene in that workflow.

You establish relationships from the initial view controller to other view controllers in the storyboard. In turn, you establish relationships from those view controllers to others, eventually connecting most or all of the storyboard’s scenes into a single connected graph. The type of relationship you establish determines when a connected view controller is instantiated by iOS, as follows:

To identify a specific view controller or segue inside a storyboard, use Interface Builder to assign it an identifier string that uniquely identifies it. To programmatically load a view controller from the storyboard, you must assign it an identifier. Similarly, to trigger a segue programmatically, it must also be assigned an identifier. When a segue is triggered, that segue's identifier is passed to the source view controller, which uses it to determine which segue was triggered. For this reason, consider labeling every segue with an identifier.

When you build an app using storyboards, you can use a single storyboard to hold all of its view controllers, or you can create multiple storyboards and implement a portion of the user interface in each. One storyboard in your app is almost always designated as the main storyboard. If there is a main storyboard, iOS loads it automatically; other storyboards must be explicitly loaded by your app.

The Main Storyboard Initializes Your App’s User Interface

The main storyboard is defined in the app’s Information property list file. If a main storyboard is declared in this file, then when your app launches, iOS performs the following steps:

  1. It instantiates a window for you.

  2. It loads the main storyboard and instantiates its initial view controller.

  3. It assigns the new view controller to the window’s rootViewController property and then makes the window visible on the screen.

Your app delegate is called to configure the initial view controller before it is displayed. The precise set of steps iOS uses to load the main storyboard is described in Coordinating Efforts Between View Controllers.

Segues Automatically Instantiate the Destination View Controller

A segue represents a triggered transition that brings a new view controller into your app’s user interface.

Segues contain a lot of information about the transition, including the following:

  • The object that caused the segue to be triggered, known as the sender

  • The source view controller that starts the segue

  • The destination view controller to be instantiated

  • The kind of transition that should be used to bring the destination view controller onscreen

  • An optional identifier string that identifies that specific segue in the storyboard

When a segue is triggered, iOS takes the following actions:

  1. It instantiates the destination view controller using the attribute values you provided in the storyboard.

  2. It gives the source view controller an opportunity to configure the new controller.

  3. It performs the transition configured in the segue.

Triggering a Segue Programmatically

A segue is usually triggered because an object associated with the source view controller, such as a control or gesture recognizer, triggered the segue. However, a segue can also be triggered programmatically by your app, as long as the segue has an assigned identifier. For example, if you are implementing a game, you might trigger a segue when a match ends. The destination view controller then displays the match’s final scores.

You programmatically trigger the segue by calling the source view controller’s performSegueWithIdentifier:sender: method, passing in the identifier for the segue to be triggered. You also pass in another object that acts as the sender. When the source controller is called to configure the destination view controller, both the sender object and the identifier for the segue are provided to it.

Listing 2-1 shows a simple method that triggers a segue. This example is a portion of a larger example described in Creating an Alternate Landscape Interface. In this abbreviated form, you can see that the view controller is receiving an orientation notification. When the view controller is in portrait mode and the device is rotated into landscape orientation, the method uses a segue to present a different view controller onscreen. Because the notification object in this case provides no useful information for performing the segue command, the view controller makes itself the sender.

Listing 2-1  Triggering a segue programmatically

- (void)orientationChanged:(NSNotification *)notification
{
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
        !isShowingLandscapeView)
    {
        [self performSegueWithIdentifier:@"DisplayAlternateView" sender:self];
        isShowingLandscapeView = YES;
    }
// Remainder of example omitted.
}

If a segue can be triggered only programmatically, you usually draw the connection arrow directly from the source view controller to the destination view controller.

Instantiating a Storyboard’s View Controller Programmatically

You may want to programmatically instantiate a view controller without using a segue. A storyboard is still valuable, because you can use it to configure the attributes of the view controller as well as its view hierarchy. However, if you do instantiate a view controller programmatically, you do not get any of the behavior of a segue. To display the view controller, you must implement additional code. For this reason, you should rely on segues where possible and use this technique only when needed.

Here are the steps your code needs to implement:

  1. Obtain a storyboard object (an object of the UIStoryboard class).

    If you have an existing view controller instantiated from the same storyboard, read its storyboard property to retrieve the storyboard. To load a different storyboard, call the UIStoryboard class’s storyboardWithName:bundle: class method, passing in the name of the storyboard file and an optional bundle parameter.

  2. Call the storyboard object’s instantiateViewControllerWithIdentifier: method, passing in the identifier you defined for the view controller when you created it in Interface Builder.

    Alternatively, you can use the instantiateInitialViewController method to instantiate the initial view controller in a storyboard, without needing to know its identifier.

  3. Configure the new view controller by setting its properties.

  4. Display the new view controller. See Displaying a View Controller’s Contents Programmatically.

Listing 2-2 shows an example of this technique. It retrieves the storyboard from an existing view controller and instantiates a new view controller using it.

Listing 2-2  Instantiating another view controller inside the same storyboard

- (IBAction)presentSpecialViewController:(id)sender {
    UIStoryboard *storyboard = self.storyboard;
    SpecialViewController *svc = [storyboard instantiateViewControllerWithIdentifier:@"SpecialViewController"];
 
    // Configure the new view controller here.
 
    [self presentViewController:svc animated:YES completion:nil];
}

Listing 2-3 shows another frequently used technique. This example loads a new storyboard and instantiates its initial view controller. It uses this view controller as the root view controller for a new window being placed on an external screen. To display the returned window, your app calls the window’s makeKeyAndVisible method.

Listing 2-3  Instantiating a view controller from a new storyboard

- (UIWindow*) windowFromStoryboard: (NSString*) storyboardName
                                   onScreen: (UIScreen*) screen
{
    UIWindow *window = [[UIWindow alloc] initWithFrame:[screen bounds]];
    window.screen = screen;
 
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
    MainViewController *mainViewController = [storyboard instantiateInitialViewController];
    window.rootViewController = mainViewController;
 
    // Configure the new view controller here.
 
    return window;
}

Transitioning to a New Storyboard Requires a Programmatic Approach

Segues connect only scenes that are stored in the same storyboard. To display a view controller from another storyboard, you must explicitly load the storyboard file and instantiate a view controller inside it.

There is no requirement that you create multiple storyboards in your app. Here, though, are a few cases where multiple storyboards might be useful to you:

  • You have a large programming team, with different portions of the user interface assigned to different parts of the team. In this case, each sub team owns a storyboard limiting the number of team members working on any specific storyboard’s contents.

  • You purchased or created a library that predefines a collection of view controller types; the contents of those view controllers are defined in a storyboard provided by the library.

  • You have content that needs to be displayed on an external screen. In this case, you might keep all of the view controllers associated with the alternate screen inside a separate storyboard. An alternative pattern for the same scenario is to write a custom segue.

Containers Automatically Instantiate Their Children

When a container in a storyboard is instantiated, its children are automatically instantiated at the same time. The children must be instantiated at the same time to give the container controller some content to display.

Similarly, if the child that was instantiated is also a container, its children are also instantiated, and so on, until no more containment relationships can be traced to new controllers. If you place a content controller inside a navigation controller inside a tab bar controller, when the tab bar is instantiated, the three controllers are simultaneously instantiated.

The container and its descendants are instantiated before your view controller is called to configure them. Your source view controller (or app delegate) can rely on all the children being instantiated. This instantiation behavior is important, because your custom configuration code rarely configures the container(s). Instead, it configures the content controllers attached to the container.

Instantiating a Non-Storyboard View Controller

To create a view controller programmatically without the use of the storyboard, you use Objective-C code to allocate and initialize the view controller. You gain none of the benefits of storyboards, meaning you have to implement additional code to configure and display the new view controller.

Displaying a View Controller’s Contents Programmatically

For a view controller’s content to be useful, it needs to be displayed on screen. There are several options for displaying a view controller’s contents:

In all cases, you assign the view controller to another object—in this case, a window, a view controller, or a popover controller. This object resizes the view controller’s view and adds it to its own view hierarchy so that it can be displayed.

Listing 2-4 shows the most common case, which is to assign the view controller to a window. This code assumes that a storyboard is not being used, so it performs the same steps that are normally done on your behalf by the operating system: It creates a window and sets the new controller as the root view controller. Then it makes the window visible.

Listing 2-4  Installing the view controller as a window’s root view controller

- (void)applicationDidFinishLaunching:(UIApplication *)application {
   UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
   levelViewController = [[LevelViewController alloc] init];
   window.rootViewController = levelViewController;
   [window makeKeyAndVisible];
}