Learning how to use to UITableView is not much tougher than material covered in the previous UIKit tutorials. Again we’re going to create a UITableView application from scratch using Interface Builder so that we have an idea of how to set things up without Apple’s help.
By now, creating a Window-Based Application should be a breeze. Do that now and call the project TableFun. Let’s go ahead and add the classes that we now we’re going to need before we start writing any code. Add a UITableViewController subclass called MyTableViewController as well as a UITableViewCell subclass called MyTableViewCell to your project.
Before switching over to interface builde make the interface portion of TableFunAppDelegate look like the following:
@interface TableFunAppDelegate : NSObject <UIApplicationDelegate> { IBOutlet UITableViewController *tableViewController; UIWindow *window; } @property (nonatomic, retain) IBOutlet UIWindow *window; @end
We’re just adding a member variable so that it will appear as an outlet in Interface Builder, this is the usual. Now make your implementation part of TableFunAppDelegate look like so:
@implementation TableFunAppDelegate @synthesize window; - (void)applicationDidFinishLaunching:(UIApplication *)application { [window addSubview:tableViewController.view]; // Override point for customization after application launch [window makeKeyAndVisible]; } - (void)dealloc { [tableViewController release]; [window release]; [super dealloc]; } @end
Again this should also look very familiar. We’re adding the view of our table view controller to the application window.
Double click on the MainWindow.xib for the project. Using the Library window add a UITableViewController instance to your nib. Go ahead and set the class of this controller to MyTableViewController (again this should all sound familiar by now).
Connect the tableViewController outlet on your app delegate to the MyTableViewController instance you just added to the nib. That’s pretty much it for Interface Builder in this tutorial.
Switch back to XCode and make the interface portion of MyTableViewController look like the following:
@interface MyTableViewController : UITableViewController { NSMutableArray *theData; } @end
Note that we are declaring an array, this array will hold the data that will go into the individual table cells. Now make the implementation of MyTableViewController look like the following:
#import "MyTableViewController.h" #import "MyTableViewCell.h" @implementation MyTableViewController - (void) viewDidLoad { [super viewDidLoad]; // must end initWithObject with nil theData = [[NSMutableArray alloc] initWithObjects:@"Abe", @"Barbara", @"Cathy", @"Donald", @"Evan", @"Fred", @"Gale", @"Hannah", nil]; [self.tableView reloadData]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview // Release anything that's not essential, such as cached data } #pragma mark Table view methods - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } // Customize the number of rows in the table view. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [theData count]; } // Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; MyTableViewCell *cell = (MyTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if(cell == nil) { cell = [[[MyTableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease]; } [cell setData:[theData objectAtIndex:[indexPath row]]]; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // Navigation logic may go here. Create and push another view controller. // AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil]; // [self.navigationController pushViewController:anotherViewController]; // [anotherViewController release]; } - (void)dealloc { [super dealloc]; } @end
That’s a lot of code, let’s break it down:
- (void) viewDidLoad { [super viewDidLoad]; // must end initWithObject with nil theData = [[NSMutableArray alloc] initWithObjects:@"Abe", @"Barbara", @"Cathy", @"Donald", @"Evan", @"Fred", @"Gale", @"Hannah", nil]; [self.tableView reloadData]; }
As soon as the table view controller’s table view loads, we create an array just to hold a bunch of NSStrings. We’ll put these into the cells of the table view. Next we tell our tableView that we need to reload the content. The UITableViewController is the data source for the table view, the table view will start calling tableView:cellForRowAtIndexPath:. But in order to know when to stop we need to tell it how many things it should display:
// Customize the number of rows in the table view. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [theData count]; }
Now let’s examine the code that actually passes a new table view cell to the table view:
// Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; MyTableViewCell *cell = (MyTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if(cell == nil) { cell = [[[MyTableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease]; } [cell setData:[theData objectAtIndex:[indexPath row]]]; return cell; }
Call the reloadData method on UITableView causes it request cells. In order to optimize UITableView display, cell objects maybe reused, that is the point of the reuseIdentifier portion. Once we have a cell we call the setData: method on our MyTableViewCell instance. This is our own method (not part of Apple’s API at all) and we’re going to implement it now. Open up the interface file for MyTableViewCell and make it look like the following:
@interface MyTableViewCell : UITableViewCell { UILabel *name; } - (void) setData:(NSString*)nameString; @end
We’re defining a member variable for a UILabel which will show a name. We also define the method for updating the display of MyTableViewCell called setData: which takes an NSString. Make the implementation of MyTableViewCell look like the following:
@implementation MyTableViewCell - (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) { // Initialization code name = [[UILabel alloc] initWithFrame:frame]; [self addSubview:name]; } return self; } - (void) setData:(NSString*)nameString { name.text = nameString; [name sizeToFit]; [self setNeedsDisplay]; } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; // Configure the view for the selected state } - (void)dealloc { [name release]; [super dealloc]; } @end
In the intialization of the cell we create the UILabel to hold the name and it as a subview. In setData: we update the text property of our name label. We size the label so that it fits exactly the dimensions of the string, we then call setNeedsDisplay so that our cell gets redrawn.
You should now be able to run the application. It will be fairly ugly. Beautification is left as an exercise for the reader. Also I challenge you to make it so that selecting a cell navigates to a new view.