Adoption Curve Dot Net

How a Navigation-based App Fits Together

| Comments

Something I found less-than-obvious when starting out with iOS development was how the different types of ap generated by the project templates available in Xcode actually worked. Depending on which you choose what happens in appDelegate varies in subtly-different ways.

What follows is the first of a series of posts that summarise the main app types, and how these start up. It starts with a basic single-view app, and steps through the process of converting this into an app which is navigation-based.

What AppDelegate does

The App Delegate class does what it says on the tin - it’s the delegate for the app that is run by main.m. When the app is up and running, the app delegate receives the application:didFinishLaunchingWithOptions: call. This is the point where the app’s main window is created and configured.

The single-view app

The “single view” is the simplest kind of app that it’s possible to create (I’m deliberately ignoring the “empty app” template, which I’ll come back in a later post). It has a single view controller which creates and manages a single view.

The application:didFinishLaunchingWithOptions: method looks like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

Stepping through this, the first line grabs a reference to the window into which the view is going to be displayed. It’s an instance of UIWindow created with the bounds of the main screen - normally this is the screen of the device, but there are situations where you can have more than one screen (connected to an external display, for example).

Then an instance of a UIViewController is created and assigned to the viewController property, using the ViewController xib file.

This UIViewController instance is assigned to the rootViewController property of the app’s window, and the makeKeyAndVisible method brings this window to the front and starts intercepting user interactions.

Finally, the method returns YES, and the rootViewController takes over.

This is the basic pattern for all iOS apps - starting a different style, a navigation-based app for example, is just a case of changing this method so that the right kind of class becomes the rootViewController.

Converting to a navigation-based app

So how would we go about firing up a navigation-based app? The first thing to do is to thing about how the iOS nagivation structure works. The view hierarchy of a navigation-based app can be thought of as a stack of paper. You start with the initial sheet. As you move forward into the hierarchy, you push new sheets onto the stack. As you move back through the hierarchy, the top-most sheet is popped off to reveal the one before it.

In iOS terms, the stack is the UINavigationController. The initial sheet of paper is the navigation controller’s rootViewController, and can also be accessed through the topViewController property. To display the next sheet, you instantiate a new UIViewController and push it onto the stack with the pushViewController:animated: method. To go back up the stack (or hierachy) you call the popViewController:animated method. At any given time, there will be a view controller being displayed, which is the navigation controller’s visibleViewController property.

That’s worth thinking over again, to make sure that you’re clear about what roles each of the rootViewControllers play. The UINavigationController is the window’s rootViewController. The UIViewController that you instantiate the UINavigationController with is the rootViewController of the navigation controller. Same name, but two different objects with two different purposes.

Having got our heads around this, let’s start setting this app up from scratch. This time, we’ll start with an Empty Application template, which looks like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Let’s start by getting rid of the stuff we don’t need:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];


    [self.window makeKeyAndVisible];
    return YES;
}

First off all, we need a UIViewController subclass that’s going to start life as the topViewController of the UINavigationController that we haven’t yet created. Create a new UIViewController subclass called FirstViewController with File -> New -> File -> Objective C class:

While you’re here, you could also add something into the nib file so that it gets shown on screen when the time comes:

Then import this into the appDelegate:

#import "FirstViewController.h"

And create an instance of it in the didFinishLaunchingWithOptions: method

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    FirstViewController *firstViewController = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];

    [self.window makeKeyAndVisible];
    return YES;
}

Now we can create the UINavigationController subclass, and set the firstViewController as its rootViewController:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.

    FirstViewController *firstViewController = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];

    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:firstViewController];

    [self.window makeKeyAndVisible];
    return YES;
}

We’re nearly there - now that we have a navController, we need to add this to the window as the rootViewController so that it becomes visible and handles user interaction:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.

    FirstViewController *firstViewController = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];

    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:firstViewController];

    self.window.rootViewController = navController;

    [self.window makeKeyAndVisible];
    return YES;
}

One final touch is to give the firstViewController a title property that will be displayed on the navigation bar:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.

    FirstViewController *firstViewController = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];

    [firstViewController setTitle:@"First View"];

    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:firstViewController];

    self.window.rootViewController = navController;

    [self.window makeKeyAndVisible];
    return YES;
}

Run the app, and you’ll see the firstViewController appear underneath the navigation bar:

At this point, the firstViewController is neatly instantiated inside the UINavigationController. Pushing a new view controller onto the stack is a process of creating the new UIViewController controller subclass in the same way as we created FirstViewController, then instantiating this in response to some kind of user input, say a button click:

-(IBAction)didClickNextButton:(id)sender {
    SecondViewController *secondVC = [SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
}

Then the new view controller gets pushed onto the stack:

-(IBAction)didClickNextButton:(id)sender {
    SecondViewController *secondVC = [SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
    [self.navigationController pushViewController:secondVC animated:YES];
}

Removing the SecondViewController instance from the view hierarchy is done by popping it:

-(IBAction)didTapBackButton:(id)sender {
    [self.navigationController popViewControllerAnimated:YES];
}

You can also pop back to a specific view controller with something like:

[self.navigationController popToViewController:previousViewController animated:YES];

And pop right back to the top of the hierarchy with

[self.navigationController popToRootViewControllerAnimated:YES];

In the next post, I’ll step through the same process for a tab-based app.

Comments