Creating a Navigation based iOS 4 iPhone Application using TableViews

From Techotopia
Revision as of 17:17, 11 February 2011 by Neil (Talk | contribs)

Jump to: navigation, search
PreviousTable of ContentsNext
Creating a Simple iOS 4 iPhone Table View ApplicationUsing the UIPickerView and UIDatePicker Components in iOS 4 iPhone Applications


<google>BUY_IOS4</google>


In the previous chapter (entitled Creating a Simple iOS 4 iPhone Table View Application) we looked at the basics of the Table View object and worked through the creation of a simple example application that contained a single table view without any application navigation capabilities. In most cases, however, table views will be used 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 iPhone applications using table views.


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 Settings applications.

When developing a navigation based application the central component of the architecture is the navigation controller. In addition, each screen has a table 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. 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.

An Overview of the Example

The goal of the example outlined in the remainder of this chapter is to design and implement an iOS iPhone application that combines both navigation and table views. When completed, the application will display a table view that lists 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.

The following figure illustrates both of the table views from the completed application:


An iOS 4 iPhone application running with table view navigation implemented


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 situation it is likely that such data would be pulled from a database, but in the interests of keeping this example as basic 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 will be needed, so start Xcode and click on the option to create a new project. On the New Project screen, select the iOS Application option from the left hand panel and highlight the Navigation-based Application option icon from the main panel. Click the Choose… button and name the project TableView.

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

The selection of the Navigation-based iOS application template has given us a number of template files that will save us a considerable amount of time in the course of creating our application. The best way to get an overview of this is to open the MainWindow.xib file in Interface Builder. Once the file has loaded, take some time to review the list of entries contained within the MainWindow.xib document window, clicking on the right pointing arrows if necessary to unfold the hierarchy for the Navigation and Root View Controller items:


<google>ADSDAQBOX_FLOW</google> The MainWindow.xib for a Navigation based iPhone application in Interface Builder


Xcode has created for us an application delegate and a navigation controller. The navigation controller contains a navigation bar and a root view controller for the home view of our application.

Though not visible in the above document window, the root view controller has associated with it an Interface Builder NIB file named RootViewController.xib (also automatically created for us by Xcode) that in turn contains a UITableView instance. To access this file, double click on the Navigation Controller item to display the design screen. This window will contain text that reads “Loaded from RootViewController”. Click on the RootViewController link to load the RootViewController.xib file into Interface Builder. In the RootViewController.xib document window, double click the Table View entry to display the table view object in the design window.

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 for our application. Begin by editing the RootViewController.h interface file and add the declaration for an array to hold the names of the authors that are going to appear on the root table view:

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

Edit RootViewController.m and add the line to synthesize access to the authorList array, then uncomment the template viewDidLoad method and 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";

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
.
.
@end

Next, modify the viewDidUnload and dealloc methods to ensure that we release 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;
}
- (void)dealloc {
    [authorList release];
    [super dealloc];
}

Writing Code to Display the Data in the Table View

When a table view is displayed, it needs to know how many rows it is going to be required to display. It finds this information by making a call to the numberOfRowsInSection method. Our RootViewController.m file contains a pre-created template of this method, currently coded to return 0. In our example we want to return a value equivalent to the number of items in our authorList array, so we need to modify this method accordingly:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)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] autorelease];
    }
        // 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 that has been requested, uses it as the index into our array of authors and assigns it to the appropriate property of the cell object that was created for us in the preceding lines of the method.

Now is a good time to make sure we haven’t 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 follows:


A tableview in an iOS 4 iPhone app


When we click on an author we want to navigate to another table view that provides a list of books written by that 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 two files and configure them to meet our 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, right click on Classes under Groups & Files in the main Xcode project window and select Add -> New File. On the template selection screen, select iOS Cocoa Touch Class followed by the UIViewController subclass icon. Make sure that the UITableViewController subclass option is selected and that the Targeted for iPad option is off. We will manually create the NIB file later so leave the With XIB for user interface option unchecked:


Adding a new table controller class to an iOS 4 iPhone application


Click on Next and name the class BooksViewController. Since we will need an interface header file, leave the Also create “BooksViewController.h” option selected and click Finish to create the files.

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 and add code to forward reference the BooksViewController class and add an outlet to the new view controller:

@class BooksViewController;

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

Before we move on to the next step, be sure to save the above changes to the RootViewContoller.h file by selecting File -> Save so that the outlet is visible to us from inside Interface Builder. Failure to save the file at this point may result in the outlet not appearing when we try to create connections from within Interface Builder.

Connecting the Second View Controller to the Root View Controller

Load RootViewController.xib into Interface Builder and drag a new View Controller (UIViewController) object from the Library window (Tools -> Library or Command + L) to the RootViewController.xib document window. Open the Identity Inspector (Tools -> Identity Inspector or Command + 4) and change the Class from UIViewController to BooksViewController:


Changing the class of the view controller


Select the File’s Owner icon and open the Connections Inspector (Tools -> Connections Inspector or Command + 2). Ctrl 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 RootViewController.xib window to make the connection to the outlet:


Adding a connection from the second view controller to the root view controller


Save the design and exit from Interface Builder.

Creating the NIB File for the Second Table View

The next step is to create the NIB file for our second view controller. This will contain the instance of the table view that we will use to display the books that the selected author has written. In the main Xcode window, right click on the Resources entry listed in the Groups & Files panel and select Add -> New File. In the New File dialog, select User Interface under the iOS list and select the View XIB icon in the main panel. Click Next and name the file BooksViewController.xib before clicking the Finish button to create the file.

Double click on BooksViewController.xib in the main project window to load it into Interface Builder. At this point, the file currently contains a UIView object when we actually want it to contain a UITableView. In the BooksViewController.xib document window select the View icon and press the Delete key on the keyboard to remove it from the design. From the Library window (Tools -> Library or Command + L) drag a Table View object into the document window. The design window will subsequently update to reflect the presence of the table view, populating it with some dummy data:


The second table view object added to our example iPhone app


If the view window does not appear as illustrated in the previous figure, simply double click on the Table View icon in the document window. Select the File’s Owner icon, display the Identity Inspector and change the class to BooksViewController. With the File’s Owner item still selected, display the Connections Inspector (Tools -> Connections Inspector or Command + 2). Click on the circle to the right of the view outlet and drag to the Table View entry in the document window. Select the Table View object and using the same technique, connect the dataSource and delegate outlets to File’s Owner. Save and exit from Interface Builder.

Implementing the Functionality of the Second View Controller

Now that we have the view controller and nib file for books table view created we now 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, retain) 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):

- (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 that correspond to the author that was selected by the user. 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 our case 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 gets 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. Remove the comments at the top and bottom of this method and modify it as follows:

- (void)viewWillAppear:(BOOL)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];
        }

        [super viewWillAppear:animated];
        [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 free it up when the table view disappears or is unloaded by making changes to the following methods:

- (void)viewWillDisappear:(BOOL)animated {
        [books release];
        [super viewWillDisappear:animated];
}
- (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 tell the table view how many rows to display and to provide the cells for those rows. These steps are much the same as those implemented for the root view controller earlier in the chapter:

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [books count];
}
- (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] autorelease];
    }
    // Configure the cell…
    cell.textLabel.text = [books objectAtIndex: [indexPath row]];
    return cell;
}

We also need to make sure we free up the memory we allocated to our array in the situation where the user exits the application while the view is displayed:

- (void)dealloc {
    [books release];
    [super dealloc];
}

Popping the View Controller off the Navigation Controller Stack

As we mentioned previously, when a new view is displayed in a navigation based application, it is pushed onto the navigation controller’s stack. When the user navigates back to the previous view, the view controller must be popped off the stack:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // Navigation logic may go here. Create and push another view controller.
    /*
    <#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];
         */
        [self.navigationController popViewControllerAnimated:YES];
}

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, uncomment 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.


<google>BUY_IOS4_BOTTOM</google>



PreviousTable of ContentsNext
Creating a Simple iOS 4 iPhone Table View ApplicationUsing the UIPickerView and UIDatePicker Components in iOS 4 iPhone Applications