UITableView – Drill down table view tutorial

Until now we saw how to create a simple table but there are times when we need to show another table view when a row is selected. This kind of a flow is called “drill down” and in this tutorial we learn how to create a drill down table view. Click on “Read more…” to learn more

Introduction
Using a drill down table view we can display some primary level of items, where a selected row will result in displaying a new table view with data related to the item selected. The process is repeated until an item has no sub level categories and it is then we display a detail view which is not a UITableView. This is the seventh tutorial in the UITableView series and does NOT inherit its source code from the previous tutorial.

Creating the project
Create a new project in XCode by selecting “Navigation-Based Application”, I have named my project “DrillDownApp” which is used in this tutorial.

Using nothing but a single UITableView
Instead of creating 3 different UITableView’s to display three different levels, we will use the same table view and the same table view controller. This philosophy is borrowed from web programming, where we only have one page to display a list of products.

Designing the flow
Before we start writing any code, let’s decide the flow of the application with some specifics. Below is a table of how the data movies when we select an item.

First Level Second Level Third Level Fourth Level Fifth Level Sixth Level
Item 1 Screen A Detail View N/A N/A N/A
Item 2 Screen B Screen C Detail View N/A N/A
Item 3 Screen D Screen E Screen F Detail View N/A
Item 4 Screen G Screen H Screen I Screen J Detail View

When the application is launched we will see the following items; “Item 1”, “Item 2”, “Item 3”, and “Item 4”. If “Item 1” is selected then we see the table view display “Screen A” and upon selecting “Screen A” we will see the detail view load. The same goes for “Item 2”, which will load a table view with one value called “Screen B” -> “Screen C” -> Detail view and so on so forth for the remaining values in the first table view.

Creating the data source
The data source for this tutorial will come from an XML file or a plist file. Create a new file under the “Resource” folder by clicking on File -> New File -> (Under MAC OS X) select Other -> Property List and save this new file by giving it a name of “Data.plist”. Let’s start by adding values to this property list file.

Select the “Root” element and create a new item by clicking on the arrows at the right.


Name the item “Rows” and change its type to “Array”. This is the array which will contain all the primary level items with its children. The user should be able to select an item in a table view and load another table view with all items under the selected item in the first table view. For us to achieve this, our array should contain a dictionary with at least two types of fields; string and a array. The string type will hold the value of the cell and the array will hold the children rows, which again will contain a n number of dictionaries where n is the number of children rows an item will have.

Create a new item “Rows” and set its type to “Dictionary”, you do not have to change the key of the dictionary. Select the dictionary item and create a new item and set its data type to string and name the key to “Title”. The name of the key which should not be changed throughout the property list file, since this is the key which will contain the value of the cell which shows up in the UITableViewCell. Set its value to “Item 1” since this is the first item that shows up in the table view. From the above table we know that selecting “Item 1” should reload the table view to display “Screen A”. Create another field under “Item 1” and change its type to an Array and set its key to “Children”. Under the “Children” item create another item of type “Dictionary” which will only contain a string value called “Screen A” with its key set to “Title”. If “Screen A” is selected we load the detail view; so we do not have to add any children at this level. Add the remaining items from the table in the property list and when you are done the property list should look like this.

Populating the data source
Now that we have created the data source, the next step is to populate it which will be done in the application delegate. Since the root element of data.plist is of type “Dictionary”, we will create a variable of type NSDictionary to hold all the data from the property list file. The header file of the application delegate changes like this

//DrillDownAppDelegate.h
@interface DrillDownAppAppDelegate : NSObject <UIApplicationDelegate> {

UIWindow *window;
UINavigationController *navigationController;

NSDictionary *data;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

@property (nonatomic, retain) NSDictionary *data;

@end

The variable “data” will hold our data source which is also synthesized in the implementation file and released in the dealloc method (code not shown here).

This is how the data is populated in applicationDidFinishLaunching method

//DrillDownAppDelegate.m
- (void)applicationDidFinishLaunching:(UIApplication *)application {

NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *DataPath = [Path stringByAppendingPathComponent:@"data.plist"];

NSDictionary *tempDict = [[NSDictionary alloc] initWithContentsOfFile:DataPath];
self.data = tempDict;
[tempDict release];

// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}

We read the “data.plist” file and read all of its data in a dictionary object using initWithContentsOfFile message which is present in the NSDictionary class.

Display the data in the table view
Since we will be using the same table view to display data at level, we need; an array to hold the data for the current level, a string to hold the item selected in the parent level and an integer value to tell us if this is the first level or not. This is how the header file for RootViewController changes

//RootViewController.h
@interface RootViewController : UITableViewController {

NSArray *tableDataSource;
NSString *CurrentTitle;
NSInteger CurrentLevel;
}

@property (nonatomic, retain) NSArray *tableDataSource;
@property (nonatomic, retain) NSString *CurrentTitle;
@property (nonatomic, readwrite) NSInteger CurrentLevel;

@end

The property “tableDataSource” contains the present level data source and mimicks the property list file. The array will contain n number of dictionary objects, where n is the number of rows at any given level. A dictionary will contain a title and an array containing the children of the present item. All the properties have been synthesized and the “tableDataSource” and “CurrentTitle” have been released in the dealloc method.

The method viewDidLoad changes like this

//RootViewController.m
- (void)viewDidLoad {
[super viewDidLoad];

if(CurrentLevel == 0) {

//Initialize our table data source
NSArray *tempArray = [[NSArray alloc] init];
self.tableDataSource = tempArray;
[tempArray release];

DrillDownAppAppDelegate *AppDelegate = (DrillDownAppAppDelegate *)[[UIApplication sharedApplication] delegate];
self.tableDataSource = [AppDelegate.data objectForKey:@"Rows"];

self.navigationItem.title = @"Root";
}
else
self.navigationItem.title = CurrentTitle;
}

If the value of the “CurrentLevel” is zero (which it will be when the application is launched) then the array is initialized and populated from the application delegate. Here we get the array whose key is set to “Rows” which will contain the primary level data and the children of every item. The title of the navigation item is set to “Root” in this case, if not the title is set to the “CurrentTitle” variable and the “tableDataSource” is left untouched. These two properties are set in tableView:didSelectRowAtIndexPath method which will look at it later.

Displaying data
In tableView:numberOfRowsInSection return the count of the array like this

//RootViewController.m
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.tableDataSource count];
}

The method tableView:cellForRowAtIndexPath changes like this

//RootViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}

// Set up the cell...
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];
cell.text = [dictionary objectForKey:@"Title"];

return cell;
}

From the data source that we created, we know that every array has at least one element of type “Dictionary” and that has at least one element with the key “Title” which is the string that gets displayed in the table view cell. After the UITableView cell is created, we get the dictionary object from the array and the value for the key “Title”, which is set to the text property of the cell.

Select a row
When a row is selected tableView:didSelectRowAtIndexPath method is called and this is how it looks like

//RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

//Get the dictionary of the selected data source.
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];

//Get the children of the present item.
NSArray *Children = [dictionary objectForKey:@"Children"];

if([Children count] == 0) {

}
else {

//Prepare to tableview.
RootViewController *rvController = [[RootViewController alloc] initWithNibName:@"RootViewController" bundle:[NSBundle mainBundle]];

//Increment the Current View
rvController.CurrentLevel += 1;

//Set the title;
rvController.CurrentTitle = [dictionary objectForKey:@"Title"];

//Push the new table view on the stack
[self.navigationController pushViewController:rvController animated:YES];

rvController.tableDataSource = Children;

[rvController release];
}
}

We first get dictionary object of the selected row and then get its children into an array. If the present level does not have any children we do not do anything for now, but if it does; we load the same table view with different set of data. We initialized the same “RootViewcontroller”, increment the “CurrentLevel” value so it is not zero, set the selected cell’s text as the “CurrentTitle”, push the view controller on the top of the stack and set the new data source to the “tableDataSource” property.

Try it out and you will be able to drill down by selecting a row.

Loading the detail view
Create a new view (using IB) and name it “DetailView”, remember the views get stored under the “Resources” folder. Create a new UIViewController in Xcode under the “Classes” folder and name it DetailViewController. Set the class of File’s owner of the “DetailView” to “DetailViewController” and connect the view outlet to the object in the view in the nib file. Please refer to this tutorial on how to load a detail view.

In viewDidLoad method of the “DetailViewController” set the title of the view to “Detail View”. This is how the code looks like

//DetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];

self.navigationItem.title = @"Detail View";
}

We will load the detail view when an item does not have any children. The tableView:didSelectRowAtIndexPath method changes like this

//RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

//Get the dictionary of the selected data source.
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];

//Get the children of the present item.
NSArray *Children = [dictionary objectForKey:@"Children"];

if([Children count] == 0) {

DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
}
else {

//Prepare to tableview.
RootViewController *rvController = [[RootViewController alloc] initWithNibName:@"RootViewController" bundle:[NSBundle mainBundle]];

//Increment the Current View
rvController.CurrentLevel += 1;

//Set the title;
rvController.CurrentTitle = [dictionary objectForKey:@"Title"];

//Push the new table view on the stack
[self.navigationController pushViewController:rvController animated:YES];

rvController.tableDataSource = Children;

[rvController release];
}
}


Conclusion

Using this design we can add any number of children to an item without changing the code. If we have to add data to the sixth level we need not create another table view, all we have to do is add another item in our data source and the code will handle the change. This design does use n number of table view objects like the example provided by Apple.

I hope you had fun reading this article as much as I had writing it. Please let me know what you think.

Happy Programming,
iPhone SDK Articles


Attachments

Suggested Readings

Leave a Reply

Your email address will not be published. Required fields are marked *