With refactoring out of the way, now it’s time to put the UI
together.Double-click the RootController.xib file in
Xcode to open it in Interface Builder. Then double-click on the View icon
in the RootController.xib window to bring up the View
window, and drag a table view from the Library window into the view.
You’ll find the table view under Cocoa Touch→Data Views in the Library window.
Center the UITableView in the
view, as shown in Figure 1. You must confirm
that you’ve dropped it as a subview of the main view by clicking the View
Mode widget on the menu bar of the RootController.xib
window and choosing List View. It should look as shown in Figure 1, with Table View
appearing under View. Save the .xib file using
⌘-S.
Switch back to Xcode to add the outlets and delegates Interface
Builder needs so that you can connect the UITableView to your code. Open the
RootController.hUITableView variable to the @interface declaration,
then declare this as a property and an IBOutlet. You also need to declare that this
class implements both the UITableViewDataSource and the UITableViewDelegate
protocols. This means that it both provides the data to populate the table
view and handles events generated by user interaction with the table
view. interface file and add a
Once you’ve done this, the RootController.h
file will look like this:
#import <UIKit/UIKit.h>
@interface RootController: UIViewController
<UITableViewDataSource, UITableViewDelegate>
{
UITableView *tableView;
}
@property (nonatomic, retain) IBOutlet UITableView *tableView;
@end
If you Option-double-click UITableViewDataSource in the declaration and
then click the documentation icon in the upper-right corner of the window
that appears (or ⌘-Option-double-click to go directly there),
you’ll see that the protocol has a number of optional methods, as well as
two mandatory methods (you must implement the methods that aren’t labeled
as “optional”). Having declared that our view controller is a UITableViewDataSource, our RootController implementation must implement
these two mandatory methods. These methods are tableView:cellForRowAtIndexPath:
and tableView:numberOfRowsInSection:. The first of
these methods returns a UITableViewCell
object; the table view will ask the data source delegate for a cell each
time a new cell is displayed in the view. The second method returns an
NSInteger determining how many sections
are in the table view. Table views can be divided into sections, and a
title added to the top of each section. For now, we’ll use just one
section (the default).
Despite what the documentation for UITableViewDelegate seems
to suggest, there aren’t any mandatory methods. However, to obtain any
sort of functionality from our table view we will at least have to
implement the tableView:didSelectRowAtIndexPath:
method.
Now we must add the implementation of those two mandatory data
source methods to the RootController
class (RootController.m). Once we have the code up
and running we’ll look at the tableView:cellForRowAtIndexPath: method in
detail. This method returns a populated table view cell for each entry
(index) in the table, and it’s called each time the view controller wants
to display a table view cell. For example, it’s called as the table view
is scrolled and a new cell appears in the view.
Here are the contents of RootController.m. I
marked in bold the lines I added to the file that the Xcode template
generated:
#import "RootController.h"
@implementation RootController
@synthesize tableView;
#pragma mark Instance Methods
(void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[tableView release];
[super dealloc];
}
#pragma mark UITableViewDataSource Methods
- (UITableViewCell *)tableView:(UITableView *)tv
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tv dequeueReusableCellWithIdentifier:@"cell"];
if( nil == cell ) {
cell = [[[UITableViewCell alloc]
initWithFrame:CGRectZero reuseIdentifier:@"cell"] autorelease];
}
return cell;
}
- (NSInteger)tableView:(UITableView *)tv
numberOfRowsInSection:(NSInteger)section
{
// Our table view will consist of only 3 cells
return 3;
}
#pragma mark UITableViewDelegate Methods
@end
1. Organizing and Navigating Your Source Code
I introduced something new in the preceding code listing: the
#pragma mark declaration. If you examine the lower-righthand pane of the Xcode
interface you’ll see that the title bar contains a filename, and
immediately to the right of this is the name of the method inside which
your cursor currently happens to be. If you click on this, you’ll see a
drop-down menu showing all the method names in the implementation (you
can access this menu easily using the Ctrl-2 keyboard shortcut). You’ll
also see the text of the pragma marks I added to the code. For large
classes, this is a convenient way to separate the methods involved in
different jobs. In this case, I’ve added marks for the instance, data
source, and delegate methods. You can also add a horizontal bar to the
method list by adding the following:
#pragma mark -
Do not add a space after the -,
as this will make Xcode think this is a text comment.
2. Connecting the Outlets
Open the RootController.xib file, and
when Interface Builder opens, set the
RootController.xib main window’s view mode to List,
and then open the View list to reveal the table view.
Next, click the Table View icon and set the Inspector window to
display the Connections Inspector (⌘-2). This reveals the dataSource and delegate outlets. Connect both of these to
File’s Owner in the main window, which in this case is the RootController class, as shown in Figure 2.
Now click on the File’s Owner icon. In the outlets section of the
Connections Inspector (⌘-2) you’ll see the tableView object that we flagged as an
IBOutlet in the
RootController.h file. Connect this with the
UITableView as shown in Figure 3.
Note: If you don’t see the tableView object, quit Interface Builder
(save your work so far), return to Xcode, and make sure you saved
RootController.h. Then open
RootController.xib in Interface Builder again; it
should appear when you select the File’s Owner icon and go to the
Connections Inspector.
We’ve reached a natural point at which to take a break. Quit
Interface Builder (be sure to save any changes) and return to Xcode. The
code should now run without crashing, although it’s not going to do very
much. So, click Build and Run (or Build and Debug) to start the
application in iPhone Simulator. Figure 4 shows what you
should see.
OK, now we have the basic table view code working, so let’s go
back to the RootController
implementation (RootController.m) and look at that
tableView:cellForRowAtIndexPath: method where
we were creating and then returning table view cells. For performance
reasons, the UITableView can reuse
cells to enhance scroll performance by minimizing the need to allocate
memory during scrolling. However, to take advantage of this ability we
need to specify a reuse identifier string. The UITableView uses this to look up existing
cells with the same identifier using the dequeueReusableCellWithIdentifier: method. If
it can’t find an unused cell with the correct identifier, it will create
one, but if an unused cell is available (perhaps it’s scrolled out of
the current view), it will reuse it:
UITableViewCell *cell =
[tv dequeueReusableCellWithIdentifier:@"cell"];
if( nil == cell ) {
cell = [[[UITableViewCell alloc]
initWithFrame:CGRectZero reuseIdentifier:@"cell"] autorelease];
}
return cell;
So far our table view isn’t that interesting, so let’s push
forward and add some content and some event handling. To do this, add an
implementation for the tableView:didSelectRowAtIndexPath: delegate
method to RootController.m. As the name suggests,
this method is called when a user clicks on a table view cell. Because
our cells are empty at the moment, we’ll also add some text to a cell
before returning it from this method. Added lines of code are shown in
bold:
#pragma mark UITableViewDataSource Methods
- (UITableViewCell *)tableView:(UITableView *)tv
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tv dequeueReusableCellWithIdentifier:@"cell"];
if( nil == cell ) {
cell = [[[UITableViewCell alloc]
initWithFrame:CGRectZero reuseIdentifier:@"cell"] autorelease];
}
cell.textLabel.text = @"Testing";
return cell;
}
- (NSInteger)tableView:(UITableView *)tv
numberOfRowsInSection:(NSInteger)section
{
// Our table view will consist of only 3 cells
return 3;
}
#pragma mark UITableViewDelegate Methods
- (void)tableView:(UITableView *)tv
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tv deselectRowAtIndexPath:indexPath animated:YES];
}
You can see the results of these additions in Figure 5.