Creating a Navigation based iOS 5 iPhone Application using TableViews

From Techotopia
Revision as of 14:17, 31 October 2011 by Neil (Talk | contribs) (New page: In the previous chapter (entitled Creating a Simple iOS 5 iPhone Table View Application) we looked at the basics of the Table View object and worked through the creation of a simple ex...)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

In the previous chapter (entitled Creating a Simple iOS 5 iPhone Table View Application) we looked at the basics of the Table View object and worked through the creation of a simple example application consisting of a single table view without any application navigation capabilities. In most cases, however, table view objects are utilized in conjunction with functionality that allows the user of the application to navigate through a variety of screens. In this chapter, therefore, we will focus on building navigation based iOS 5 iPhone applications using table views.

In the next chapter, entitled Using Xcode Storyboarding on iOS 5, we will explore an alternative method for adding screen navigation to iPhone applications quickly and easily.

Before proceeding with this chapter it is important to note that older versions of Xcode (in other words versions prior to Xcode 4.2) included a template option for the creation of table based navigation applications. Sadly, this option is no longer available. That said, and as will be demonstrated in this chapter, it is relatively straightforward to manually create the project structure necessary to implement this behavior, and doing so actually gives the developer a better understanding of how this is achieved.


Contents


Understanding the Navigation Controller

Navigation based applications present a hierarchical approach to displaying information to the user. Such applications typically take the form of a navigation bar (UINavigationBar) and a series of Table based views (UITableView). Selecting an item from the table list causes the view associated with that selection to be displayed. The navigation bar will display a title corresponding to the currently displayed view together with a button that returns the user to the previous view when selected. For an example of this concept in action, spend some time using the iPhone Mail or Music applications.

When developing a navigation-based application the central component of the architecture is the navigation controller. In addition, each screen has a view and a corresponding view controller. The navigation controller maintains a stack of these view controllers. When a new view is displayed it is pushed onto the navigation controller’s stack and becomes the currently active controller. The navigation controller automatically displays the navigation bar and the “back” button. When the user selects the button in the navigation bar to move back to the previous level, that view controller is popped off the stack and the view controller beneath it moved to the top becoming the currently active controller.

The view controller for the first table view that appears when the application is started is called the root view controller. The root view controller cannot be popped off the navigation controller stack.

An Overview of the Example

The goal of the example outlined in the remainder of this chapter is to design and implement an iOS 5 iPhone application by combining both navigation and table views. When completed, the application will display a table view listing book authors. When an author is selected from the table, a second table view will be displayed listing the books written by that author. The navigation bar on the books view will contain a button to navigate back to the list of authors. Figure 17-1 illustrates both of the table views from the completed application:


The completed iPhone iOS 5 TableView navigation example application

Figure 17-1


Since the objective of this chapter is to outline the steps involved in working with navigation and table views, rather than the handling of data, we will be implementing very simple code to create the authors and books data sources. In a real world scenario it is more likely that such data would be pulled from a database, but in the interests of keeping this example as uncomplicated as possible we will be hard coding author names and book lists into simple array objects.


Setting up the Project

In order to implement our example application a new project is needed, so start Xcode and click on the option to create a new project or select the File -> New -> New Project… menu option. Select the iOS Application option from the left hand panel and highlight the Empty Application icon from the main panel. On the next screen enter TableView as the product name and class prefix, unset the Use Storyboard toggle, click Next and choose a suitable location for the files before creating the project.

The main Xcode window will appear populated with a number of different template files ready for us to begin to create our application.

Reviewing the Project Files

Due to the selection of the Empty Application project template Xcode has generated a minimal project containing just the application delegate files. The first step, therefore, is to add the root view controller to the project.

Adding the Root View Controller

As previously discussed, a navigation controller needs to have a root view controller assigned to it. The view associated with the root view controller is displayed to the user as the first screen when the navigation controller is instantiated. Clearly before we can begin to think about adding a navigation controller to our application we first need to create a root view controller and corresponding view. The root view controller must be a UIViewController class or a subclass thereof. Since the objective of this example is to implement table view based navigation we will use a UITableViewController instance.

Hold down the Ctrl key on the keyboard and click with the mouse button on the TableView target at the top of the project navigator panel and select New File… from the resulting menu. In the subsequent Choose a template for your new file panel select Cocoa Touch from beneath iOS in the left hand panel and choose the UIViewController subclass icon:


Adding a new view controller to an Xcode iPhone iOS 5 project

Figure 17-2


Click on the Next button and on the subsequent screen change the Subclass of menu option to UITableViewController. Verify that the With XIB for User Interface option is selected and name the class RootViewController. Click Next and create the new class. Xcode will subsequently add three new files to the project (named RootViewController.h, RootViewController.m and RootViewController.xib) for our root view controller to the project.

Creating the Navigation Controller

With the root view controller added to the project the next step is to add some code to create a navigation controller and then assign the root view controller to it. The ideal place to initialize the navigation controller is within the App Delegate. First we need to declare a reference to the navigation controller in the app delegate’s interface file. Select the TableViewAppDelegate.h file from the project navigator panel and modify it to forward reference our RootViewController class and to add a reference to the navigation controller that we will be creating in our code later:

#import <UIKit/UIKit.h>

@class RootViewController;

@interface TableViewAppDelegate : UIResponder <UIApplicationDelegate>

{
    UINavigationController *navigationController;
}
@property (strong, nonatomic) UINavigationController *navigationController;
@property (strong, nonatomic) UIWindow *window;
@end

Next, select the TableViewAppDelegate.m implementation file and modify it to import the RootViewController.h interface file and synthesize accessors for the navigation controller:

#import "TableViewAppDelegate.h"
#import "RootViewController.h"

@implementation TableViewAppDelegate

@synthesize window = _window;
@synthesize navigationController;

The last step in setting up the view controller is to modify the didFinishLaunchingWithOptions method. Comment out the current lines in the method and modify it as follows:

- (BOOL)application:(UIApplication *)application
 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    UIViewController *rootController = 
         [[RootViewController alloc] 
         initWithNibName:@"RootViewController" bundle:nil];

    navigationController = [[UINavigationController alloc]
                 initWithRootViewController:rootController];

    self.window = [[UIWindow alloc] 
        initWithFrame:[[UIScreen mainScreen] bounds]];
    [self.window addSubview:navigationController.view];
    [self.window makeKeyAndVisible];
    return YES;

/*
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [self.window makeKeyAndVisible];
    return YES;
*/
}

The method begins by creating an instance of our root view controller class, initializing it with the RootViewContoller NIB user interface file and assigning the object reference to a variable named rootController. Next, a UINavigationController instance is created and assigned to the navigationController variable. As part of the navigation controller initialization process our newly created rootController is assigned as the navigation controller’s root view controller, thereby ensuring that this is the first view displayed to the user.

In terms of creating the navigation controller this is all that needs to be done. The remainder of the work to complete this project involves setting up the views that will be displayed to the user.

Setting up the Data in the Root View Controller

The root view controller is going act as the data source and app delegate for the table view that displays the list of authors. As such, we need to add some code to set up this data and override some methods to implement the functionality of our application. Begin by editing the RootViewController.h interface file and adding the declaration for an array to hold the names of the authors who are going to appear on the root view controller’s table view:

@interface RootViewController : UITableViewController {
        NSArray *authorList;
}
@property (nonatomic, strong) NSArray *authorList;
@end

Edit RootViewController.m and add the line to synthesize access to the authorList array then edit the viewDidLoad method to implement the code to initialize the authorList array and set the title of the root table view to Authors:

#import "RootViewController.h"

@implementation RootViewController
@synthesize authorList;
.
.
.
- (void)viewDidLoad {
   [super viewDidLoad];
   self.authorList = [[NSArray alloc] 
      initWithObjects:@"Clancy, Thomas", 
      @"Lehane, Dennis", nil];
   self.title = @"Authors";
}
.
.
@end

Next, modify the viewDidUnload method to ensure that we indicate that we are finished with the authorList array when the view unloads or the application exits:

- (void)viewDidUnload {
    // Release anything that can be recreated in viewDidLoad or on demand.
    // e.g. self.myOutlet = nil;
    self.authorList = nil;
}

Writing Code to Display the Data in the Table View

When a table view is displayed, it needs to know how many sections and rows it is going to be required to display. It finds this information by making calls to the numberOfSectionsInTable and numberOfRowsInSection methods. Our RootViewController.m file contains pre-created templates for these methods, currently coded to return 0. In our example we want to return 1 for the number of sections and a value equivalent to the number of items in our authorList array, so we need to modify these methods accordingly (removing the #warning lines in the process):

- (NSInteger)numberOfSectionsInTableView:
(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)
tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [self.authorList count];
}

Having identified the number of items to be displayed, the table view then needs to get a UITableViewCell object containing the data to be displayed for each row. It achieves this by calling the cellForRowAtIndexPath method for each row, passing through the current row as an argument. When called, this method identifies the row being requested and constructs and returns a cell object. Implement the code to return a table cell for each row of the table view by modifying the template method as follows:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  static NSString *CellIdentifier = @"Cell";

  UITableViewCell *cell = [tableView 
    dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
     cell = [[UITableViewCell alloc] 
       initWithStyle:UITableViewCellStyleDefault
     reuseIdentifier:CellIdentifier];
  }
     // Configure the cell.
      cell.textLabel.text = 
       [self.authorList objectAtIndex: 
       [indexPath row]];
    return cell;
}

Note that Xcode has already written most of the code for us. In fact the only line we have to add is as follows:

cell.textLabel.text = [self.authorList objectAtIndex: [indexPath row]];

This line of code simply identifies the row requested, uses it as the index into our array of authors and assigns it to the appropriate property of the cell object created for us in the preceding lines of the method.

Now is a good time to make sure we have not made any errors in our coding, so build and run the application to make sure it all works so far. Assuming all is well, the application should appear in the simulator as shown in Figure 17-3:


iPhone iOS 5 Table View application first table displayed

Figure 17-3


When the user selects an author we want to navigate to another table view listing the books written by the chosen author. That table view will have its own NIB file containing the table view object and also its own view controller. The next steps, therefore, are to create these objects and configure them to meet our project requirements.

Creating the Second View Controller

To create the view controller for the table view that will display the list of books written by the selected author, Ctrl-click on the TableView entry in the project navigator panel and select New File. On the template selection screen, select iOS Cocoa Touch Class followed by the UIViewController subclass icon and click Next. On the next screen, select UITableViewController from the Subclass of menu and make sure that the With XIB for user interface option is on. Name the new class BooksViewController and proceed with the class creation process.

Before we begin work on our second table view we need to make sure that the new class is declared within our root view controller class. To do so, edit the RootViewController.h file, add code to forward reference the BooksViewController class and add an outlet to the new view controller:

#import <UIKit/UIKit.h>

@class BooksViewController;

@interface RootViewController : UITableViewController
{
            NSArray *authorList;
            BooksViewController *booksController;
}
@property (nonatomic, strong) NSArray *authorList;
@property (nonatomic, strong) IBOutlet BooksViewController *booksController;
@end

Connecting the Second View Controller to the Root View Controller

Having created an outlet for the books view controller within our root view controller class the next step is to connect that outlet to the books controller object. Select the RootViewController.xib file and drag a new View Controller (UIViewController) object from the Object library panel (View -> Utilities -> Show Object Library) to the Objects panel. Display the Identity Inspector (View -> Utilities -> Show Identity Inspector) and change the Class from UIViewController to BooksViewController:


Changing the bookController class

Figure 17-4


Select the File’s Owner entry listed under Placeholders and open the Connections Inspector (View -> Utilities -> Show Connections Inspector). Click in the circle located to the right of the booksController outlet and drag the blue line to the Books View Controller item in the Objects panel to make the connection to the outlet:


Establishing an outlet connection in Xcode

Figure 17-5


== Implementing the Functionality of the Second View Controller

Now that we have the view controller and nib file for the books table view created we need to implement the functionality of this view controller. Since the BooksViewController class is acting as the dataSource for the table view we need to add some code to store the names of the books written by the respective authors.

Edit the BooksViewContoller.h interface file and make the following changes to declare an array to hold the book titles:

#import <UIKit/UIKit.h>

@interface BooksViewController : UITableViewController {
        NSArray *books;
}
@property (nonatomic, strong) NSArray *books;
@end

Next, edit BooksViewController.m and synthesize the accessors for the new array:

#import "BooksViewController.h"

@implementation BooksViewController
@synthesize books;
.
.
@end

It is also necessary to complete the coding for two template methods located in the BooksViewController.m file that provide information about how many sections the books table view contains (in this case one) and the number of rows in that section (in other words the number of books in our book array). Once again, note the removal of the #warning directives:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // Return the number of sections.
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView 
numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    return [books count];
}

The next task is to write some code to populate the array with book titles corresponding to the selected author. One place to initialize the data is in the viewDidLoad method. This method gets called once when the view is first loaded and is not then called again unless the view had to be unloaded by the operating system in order free up memory and is then reloaded. This is ideal for situations where data needs to be initialized only once. In this case, however, we are going to initialize the books array with different data depending on which author was selected. We therefore need to choose a method that is called each time the view becomes visible. An ideal candidate for this purpose is the viewWillAppear method which gets called each time, and just before, the view is made visible to the user. Locate and modify this method as follows:

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];

  if ([self.title isEqualToString:@"Lehane"]) {
         books = [[NSArray alloc ] 
          initWithObjects:@"Mystic River", 
          @"Shutter Island", nil];
  } else {
          books = [[NSArray alloc ] 
           initWithObjects:@"The Hunt for Red October", 
           @"Red Storm Rising", nil];
  }
  [self.tableView reloadData];
}

In the above method we are using the title of the navigation bar to identify which author has been selected from the list (we will be implementing code to set the title in the root view controller later in the chapter). Based on this value we are initializing the array with the appropriate list of books. Finally, we make a call to the reloadData method of the table view to make sure that the new data is displayed (omitting this call will result in old data appearing).

Because we allocated memory to our books array in the previous method, we now need to make sure we let the system know when we no longer need it:

- (void)viewDidUnload {
   // Release any retained subviews of the main view.
   // e.g. self.myOutlet = nil;
   self.books = nil;
}

Next we need to implement the code to provide the cells for the rows in the table. These steps are much the same as those implemented for the root view controller earlier in the chapter:

- (UITableViewCell *)tableView:(UITableView *)tableView 
cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  static NSString *CellIdentifier = @"Cell";
  UITableViewCell *cell = [tableView 
   dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
       cell = [[UITableViewCell alloc] 
        initWithStyle:UITableViewCellStyleDefault
        reuseIdentifier:CellIdentifier];
  }
  // Configure the cell…
  cell.textLabel.text = [books objectAtIndex: 
     [indexPath row]];
  return cell;
}

Adding the Navigation Code

Next we need to modify our root view controller so that the books table view is displayed when the user selects an author from the list. When a user selects an item from a Table View, the didSelectRowAtIndexPath method is called so we need to override this method in our view controller.

Edit the RootViewController.m file and begin by importing the BooksViewController.h interface header file for the books view controller and then synthesize access to the booksController class:

#import "RootViewController.h"
#import "BooksViewController.h"

@implementation RootViewController
@synthesize authorList;
@synthesize booksController;

Remaining within the RootViewController.m file, locate the template didSelectRowAtIndexPath method and modify it as follows:

- (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    /*
    <#DetailViewController#> *detailViewController = 
       [[<#DetailViewController#> alloc] 
       initWithNibName:@"<#Nib name#>" 
       bundle:nil];
   // ...
   // Pass the selected object to the new view controller.
   [self.navigationController 
      pushViewController:detailViewController 
      animated:YES];
   [detailViewController release];
   */
   if (0 == indexPath.row)
   {
       self.booksController.title = @"Clancy";
   } else {
        self.booksController.title = @"Lehane";
   }
   [self.navigationController 
      pushViewController:self.booksController 
      animated:YES];
}

In this method we identify which row has been selected by the user and set the title property of the booksController object to reflect the corresponding author name. We then push the books view controller onto the navigation controller stack so that it becomes the currently active view controller, which in turn causes the books table view to become visible.

Finally, build and run the application. Selecting an author from the first table view should cause the second table view to appear listing the corresponding books for the selected author. The books view will contain a navigation bar button labeled Authors. This button was provided by the navigation controller and, when selected, will pop the books view controller off the navigation stack and return the user to the list of authors in the root view controller.

Controlling the Navigation Controller Stack Programmatically

Within the example outlined in this chapter we have only had to programmatically push a view controller onto the navigation controller stack once leaving the navigation controller to handle the “popping” operation. In practice, however, there are often instances where code will need to be written to push and pop items on and off the stack to initiate user interface navigation. The UINavigationController class includes a number of methods that can be used in code for this purpose:

  • pushViewController:animated – Makes the specified view controller the top most item on the navigation controller stack, presenting the corresponding view to the user.
  • popViewControllerAnimated – Removes the top most view controller from the navigation controller stack making the next view controller the currently active controller. This method is called automatically by the navigation bar “back” button provided by the navigation controller. Note when calling this method that it is not possible to pop the root view controller from the navigation controller stack.
  • setViewControllers:Animated – May be used to return the navigation controller stack to the state it was in the last time the application was executed. Note that this will require state information to be saved by the application on exit.
  • popToRootViewControllerAnimated – Pops all view controllers except the root view controller from the navigation controller stack.
  • popToViewController:animated – Pops all view controllers off the stack until the specified view controller is reached.
  • setViewControllers:animated – Pops all current view controllers (except the root view controller) from the stack and replaces them with the view controllers contained in the specified array.

Summary

The UINavigationController class is key to implementing navigation within iOS 5 iPhone applications. Pushing and popping view controllers on and off the navigation controller’s stack allows the user to navigate through multiple levels of screen. The navigation controller also makes the job of the programmer easier by automatically implementing both the navigation bar and a “back” button for each view.

In this chapter we have worked through an example application demonstrating the use of a navigation controller to implement table view based navigation. This, however, is just one of a number of alternate ways to implement navigation. One such alternative, and the topic of the next chapter, is to use the storyboarding feature of the latest version of Xcode.