Tuesday, May 26, 2009

iPhone tutorial: UITableView from the ground up, part 3

Now that we understand the really basic stuff about the UITableView and UITableViewCell classes, we're ready to move on to some simple interaction. We'll continue modifying the Table1 project from part 1 and part 2 so please check those parts out before reading any further.

Classes/Table1AppDelegate.m

Modify the 'cellForRowAtIndexPath' method to make it look like this:

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

// try to retrieve "cell 1" from the UITableView cache
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell 1"];
if ( cell == nil ) {
// "cell 1" wasn't present in the cache, so create it
cell = [[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"cell 1"];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
NSLog(@"creating cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);
} else {
// "cell 1" was present in the cache, so log that we're reusing it
NSLog(@"reusing cell '%@' (%p) for row %d...", cell.reuseIdentifier, cell, indexPath.row);
}
  
cell.text = [NSString stringWithFormat:@"this is row %d", indexPath.row];
return cell;
}

The only new thing here really is the 

cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;

row which says that we want a standard "detail disclosure" button to the right of the text in our cell. This is the standard way in the iPhone user interface to indicate that touching this cell will take you to a new screen. Just by adding this you also instruct the table view to start sending 'accessoryButtonTappedForRowWithIndexPath' to its 'delegate' object.

In order to receive those messages we have to implement the appropriate method, so add the following to the end of the file.

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
NSLog(@"accessoryButtonTappedForRowWithIndexPath: row=%d", indexPath.row);
}

A short note on the use of indexPath in combination with table views

You might have noticed that a lot of the delegate methods for the table view have an 'indexPath' argument of type NSIndexPath. I wrote quite a lot about index paths in a previous post so have a look there if you want a full explanation. Here I will just mention that the index paths used in conjunction with table views have two levels which represent the section and the row indices of the table. As an alternative solution two arguments 'section' and 'row' could have been used instead of the 'indexPath' argument. If the table only has one section all you need to care about is the row index, which you read from the indexPath.row property. 

Test run

Build and run the project in XCode (CMD-Return), wait for the simulator to start and then go back to XCode to bring the XCode console window to the front (CMD-R). Touch a few disclosure buttons and you should see something like this in the console window:

009-05-26 17:07:24.807 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=0
2009-05-26 17:07:27.655 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=1
2009-05-26 17:07:29.471 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=2
2009-05-26 17:07:30.943 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=3
2009-05-26 17:07:35.919 Table1-2[11521:20b] accessoryButtonTappedForRowWithIndexPath: row=8

What you do with these messages is completely up to you, but a very common way of using this information is to switch to another table view which shows information "on the next level" in some kind of hierarchical data. You often see this when table views are used together with navigation controllers since they offer a very easy way of switching to a new view as well as providing a way to get back to the last one (the "back button").

Classes/Table1AppDelegate.m

Another way of adding interaction to a table is by detecting "selections", that is detecting that the user touched a row/cell in the table. Above we detected that the user touched the "disclosure button", but here we'll detect touches to anywhere outside of the disclosure button. Add the following to the end of the file.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"didSelectRowAtIndexPath: row=%d", indexPath.row);
}

Test run

Build and run the project and touch a few rows once the simulator has started. You should see something like this in the XCode console window:

2009-05-26 17:12:00.289 Table1-2[11530:20b] didSelectRowAtIndexPath: row=1
2009-05-26 17:12:02.770 Table1-2[11530:20b] didSelectRowAtIndexPath: row=2
2009-05-26 17:12:03.882 Table1-2[11530:20b] didSelectRowAtIndexPath: row=5
2009-05-26 17:12:05.322 Table1-2[11530:20b] didSelectRowAtIndexPath: row=8

If you are really observant you probably noticed that the row you touched stays selected (as indicated by the blue colour). This is because it is our responsibility to deselect it, which is accomplished by calling 'deselectRowAtIndexPath':

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"didSelectRowAtIndexPath: row=%d", indexPath.row);
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

Summary

Allowing the user to interact with the table view opens up for a lot of possibilities and it is nice to know that the detection of user interactions is as easy as this. At least for simple interactions.

1 comment: