iPhone OS: UITableView
Many people don’t understand how to use a UITableView, and that’s okay. In truth, a UITableView is one of the easiest parts of the iPhone OS API to use – you simply have to know the names of the functions, and have a good understanding of Objective C – particularly delegation, as that is the form of data input the UITableView uses. If you are unaware of what delegation is, you can look at Apple’s Documentation on delegation.
Creating a UITableView is easy. Mostly, UITableViews are inserted into UINavigationController’s, so we’ll do that too.
This is the initialisation code in AppDelegate.m:
- (id) init { if(self = [super init]) { window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; UITableViewController *tableViewController = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain]; navigationController = [[UINavigationController alloc] initWithRootViewController:tableViewController]; [tableViewController release]; } return self; }
However, since the UITableView uses delegation as a datasource and for appearances, we need to subclass UITableViewController and change the required methods.
Once we’ve replaced all instances of ‘UITableViewController’ with our custom controller (I have named mine DBTableViewController) then we can begin to alter the delegates methods. The required methods for a class that conforms to the UITableViewDataSource and the UITableViewDelegate protocols are as follows:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
By responding to these methods, we can alter the appearance of the UITableView. For example, if we wanted to populate our custom UITableView class with cells labelled 1 through to 10 with 10 cells, we could use the following code:
#define kNumberOfSections 2 #define kNumberOfRows 10 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Set up how many 'sections' (groups of rows) return kNumberOfSections; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Set up how many rows to have in our table view return kNumberOfRows; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"CustomCell"; /* All this 'dequeueReusableCellWithIdentifier' stuff does is check whether an instance of the same cell (with the same identifier) has been deallocated yet. They are autoreleased, which means there may be one available to reuse, which would save memory. Memory is vital on the iPhone! */ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; } cell.textLabel.text = [NSString stringWithFormat:@"Row %d", indexPath.row]; cell.detailTextLabel.text = [NSString stringWithFormat:@"Section %d", indexPath.section]; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // Deselect this row, to conform the human interface guidelines [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; }
The result would look like this:
However, if we wanted to build a UITableView from an array of items, it would be a slightly different story. For a start we would have to create a storage class for our data (an NSObject subclass) called (in our case) DBItem. DBItem looks like this:
@interface DBItem : NSObject { UIImage *thumbnail; NSString *title; NSString *shortDescription; } @property (nonatomic, retain) UIImage *thumbnail; @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *shortDescription; - (BOOL) hasThumbnail; @end // DBItem.m #import "DBItem.h" @implementation DBItem @synthesize thumbnail; @synthesize title; @synthesize shortDescription; - (void) dealloc { [thumbnail release]; [title release]; [shortDescription release]; [super dealloc]; } - (id) init { if(self = [super init]) { // Any initialisation } return self; } - (BOOL) hasThumbnail { return (thumbnail) ? YES : NO; } @end
As you can see, all it is is a storage class. It simply contains a UIImage named ‘thumbnail’, has a name (an NSString*) that we called ‘title’ and a ‘shortDescription’ of the same type as the title. We could have just used an NSDictionary – however it is difficult to keep track of what is inside of the NSDictionary, whereas this class only ever contains these three properties. We are going to create a list of these items, and insert them into an NSMutableArray, which will be used by the UITableView as the source of its information.
I have imported our new storage class into the current script using:
#import "DBItem.h"Also, we need to set up the NSMutableArray in our custom UITableViewController subclass.
@interface DBTableViewController : UITableViewController { NSMutableArray *itemArray; } @property (nonatomic, retain) NSMutableArray *itemArray; @end
Don’t forget to @synthesize itemArray in the .m file!
Now we can initialise and add a list of DBItem classes to the array.
#define kNumberOfItems 6 - (void)dealloc { [itemArray release]; [super dealloc]; } - (id)initWithStyle:(UITableViewStyle)style { if (self = [super initWithStyle:style]) { // Set the title of this controller self.navigationItem.title = @"A List Of Items"; // Initialise itemArray itemArray = [[NSMutableArray alloc] initWithCapacity:kNumberOfItems]; // Insert a list of DBItem classes into itemArray for(unsigned i = 0; i < kNumberOfItems; i ++) { NSString *pathForImage = [NSString stringWithFormat:@"image%d.jpg", i]; NSString *title = [NSString stringWithFormat:@"Thumbnail %d", i]; NSString *description = [NSString stringWithFormat:@"Description for item %d", i]; UIImage *thumbnail = [UIImage imageNamed:pathForImage]; DBItem *item = [[DBItem alloc] init]; [item setTitle:title]; [item setShortDescription:description]; [item setThumbnail:thumbnail]; [itemArray addObject:item]; [item release]; } } return self; }
All we’re doing here is defining how many items we’ve got, and then setting up strings. Title will look like ‘Thumbnail 0′ or 1, 2, 3, … etc, and description will look like ‘Description for item 0′, 1, 2, 3… etc. The images are named image0.jpg, image1.jpg, image2.jpg, image3.jpg until the end of the row count, which is kNumberOfItems. These items are then accessed by the array later on, as so:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Set up how many 'sections' (groups of rows) return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Set up how many rows to have in our table view return [itemArray count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"CustomCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; } DBItem *item = [itemArray objectAtIndex:[indexPath row]]; cell.imageView.image = [item thumbnail]; cell.textLabel.text = [item title]; cell.detailTextLabel.text = [item shortDescription]; return cell; }
All we’re doing here is setting the contents of the UITableViewCell to the information stored inside our DBItem class. We could easily store our array of DBItem’s inside a singleton instance, known as a data manager. This is a unique program design that is easy to upkeep and is generally nice to get into the habit of doing. There will be an article here on singleton instances eventually.
However, for now, we’ll finish UITableView’s. We have purposely left the data array as mutable so that we can edit it later. We’re going to do that now, by using the UITableView’s editing mode. This is activated as follows:
- (void)viewDidLoad { [super viewDidLoad]; self.navigationItem.rightBarButtonItem = self.editButtonItem; }
The line ‘self.navigationItem.rightBarButtonItem = self.editButtonItem’ adds a button to the right hand side of the navigation bar which contains the word ‘edit’ or ‘done’ depending on the UITableView’s editing mode. You’ve probably seen it before in, well, practically any app. This doesn’t complete editing yet! As with everything with UITableView’s, we need to set up the delegate methods:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { // return NO if we don't want this specific cell to be edited. if([indexPath row] == 0) { // this will make the first cell uneditable. return NO; } else { // every other cell is editable! return YES; } } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // this means the user has committed a 'delete action' // something such as swiping it then pressing 'delete' or hitting edit // then tapping to select that row, and pushing delete // Need to remove it from the array [itemArray removeObjectAtIndex:[indexPath row]]; // Animate a deletion of it from the UITableView [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES]; } else if (editingStyle == UITableViewCellEditingStyleInsert) { // This is when the user has chosen to add a cell to the UITableView // However, we don't need this now! } } - (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { // This method is just telling us that the user has rearranged the views // They have moved row 'fromIndexPath' to row 'toIndexPath' // So we need to change our data accordingly [itemArray exchangeObjectAtIndex:[fromIndexPath row] withObjectAtIndex:[toIndexPath row]]; } - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { // This is asking whether a certain cell should be able to be reordered or not. // We'll disable row 0 but enable all else if([indexPath row] > 0) return YES; else return NO; }
After this, you should have a result much like this (except with different images):
And that’s pretty much all there is to know! To know how to save the final edited array, so that it can be used the next time the user re-opens the app, look into NSCoding and NSData saving to a file. I’ll add to this article at some point with more information about this! But until then, enjoy!


