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;
@endThe 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;
@endThe 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

31 comments:
I cannot run the source code. There's an error: "checking dependencies"->there's no SDK with specified name or path 'Unknown Path'. Can I ask you how to solve this error?
Thanks
ok, i made mine a bit different. Is it possible to make all the detail views different? And is it possible to use a .plist as tableview in one of the levels?
Hey
Is it possible that every Detail View is different (maybe make it in IB, so it is easy to add a picture)?
Like
First Level Second Level Third Level Fourth Level
Item 1 Screen 1 Screen 1,1 Detail View 1
Screen 1,2 Detail View 1,2
Item 2 Screen 2 Screen 2,1 Detail View 2
Screen 2,2 Detail View 2,2
Hmm hope you understand :)
Also how the hell do you change so is say "Back" and not the name of the previous page. In the navigation bar.
This code is great, I love how recursive it is.. very elegant. For some reason it works great in the simulator but on the iPhone I get a blank table.
Sorry for last comment, I just picked up that the DataPath is case sensitive,
NSString *DataPath = [Path stringByAppendingPathComponent:@"data.plist"];
"Data.plist" needs a capital D thats why it only works in the simulator.
Thanks again for your excellent blog!
First off, thanks for a FANTASTIC tutorial. I've followed your tutorials for a while, and literally 2 days ago I was scouring the web for something exactly like this (flexible, multi-level drill down).
I do have a question. What is the performance difference between using this method (XML, plist) vs. using a Sql database method? I actually prefer the XML style, but my next project looks to have a pretty heavy chunk of data and I'm concerned I'll have resource or speed issues.
Again, thanks for a great article; I can't wait to go through it.
First off, thanks for a FANTASTIC tutorial. I've followed your tutorials for a while, and literally 2 days ago I was scouring the web for something exactly like this (flexible, multi-level drill down).
I do have a question. What is the performance difference between using this method (XML, plist) vs. using a Sql database method? I actually prefer the XML style, but my next project looks to have a pretty heavy chunk of data and I'm concerned I'll have resource or speed issues.
Again, thanks for a great article; I can't wait to go through it.
@Siwei Luo
Do you have the correct version of the iphone sdk installed?
Happy Programming,
iPhone SDK Articles
@drudoo
Yes, every detail view can be different, just follow the same logic I have used in tableView:didSelectRowAtIndexPath. Based on the value selected in the parent table view controller load a different nib file.
Are you looking to create relationship's among plist files? You can do it but, I think it will be best if you create a UITableViewController for every child data.
I hope this helps.
Happy Programming,
iPhone SDK Articles
@Josh SQLite is a relational database and it depends what kind of application you are trying to build. I think use XML if you will be storing user preferences. If you need to maintain relational data involving multiple tables SQLite is the best option.
If you decide to use SQLite then take a look at SQLitePersistentObjects http://code.google.com/p/sqlitepersistentobjects/ with which you will not have to write any SQLite code. The library does everything.
Happy Programming,
iPhone SDK Articles
Fantastic resource - thanks so much!
Anything we should look out for when adding (textview) content to the final DetailView.xib. Assuming that content will reside in the plist as well.
What method of adding a search bar do you recommend?
@KT In a plist file I think it is important that all the keys have the same name so as long as you do that you can have text for the detail view in the plist file.
You can read this tutorial on how to search a UITableView http://www.iphonesdkarticles.com/2009/01/uitableview-searching-table-view.html
Happy Programming,
iPhone SDK Articles
Almost there... I replaced the UITextView (Latin template) with a 'UILable' (static text) in DetailView.xib, and am attempting to populate it from the plist, keeping the keys all same as you suggested.
Static text inside DetailViewController.m works:
lblText.text = @"Details and words galore.";
...but this gives me an empty lable (no errors):
lblText.text = CurrentTitle;
Can you please help me to understand what I missed, thanks.
@KT is it possible for yous end me an email with the source code, this way I can take a better look.
Happy Programming,
iPhone SDK Articles
It seems that for Item 4, the application doesnt drill all the way down to Screen J. Is that intentional?
@dylan: thanks for the remark, I was wondering why it did not work, and substituting data.plist for Data.plist made it work! :-)
@ Siwei Luo
I experienced the same problem in Xcode 2.2 but it vanished after I updated Xcode. You need to download and install iPhone SDK 2.2.1 as the app uses that version.
Hi, thanx for your great work. As KT i'm trying to add different content (textview) to the final DetailView, based on the last choice. Can you post here the explanation please? Thanx!
Great tutorial, I have learnt so much from it however I am now stuck and don't even know where to begin.
You show how to load data into the detail view by sending the text from the original array to the label, however it would be awesome to know how to load something more into the detail view.
I basically have 10 items in the root table, and then another 10 in the next table and then those ten lead to a detail view, but I need each detail view to show different text (preferably a viewful so labels aren't sufficient).
How do I do this? could someone point me in the direction of a tutorial for this preferably one which could be added to the exisiting examples?
Thanks.
@Twimfy You can create different detail view controllers to display different kind of detail data. This way you will be able to load a different view controller.
Hope this helps.
You are welcome to send me an email and I will be happy to look over your code.
Happy Programming,
iPhone SDK Articles
@CN that is a very good catch. I misspelled a key in the plist file and that is the reason why the app did not drill down all the way to the last value.
The code has been fixed and updated.
Happy Programming,
iPhone SDK Articles
Thanks, but I can't find your email address lol.
I understand what you're saying but I wouldn't even know how to do that or link it in, I'm surviving merely by just about understanding what is going on in the code but writing and initiating things myself is still a bit much. I'm probably just a bit too inexperienced to be dabbling in this really.
My code is very similar to that of the product of the drill down app the tutorial ends with. Except my plist is altered to put more cells and rows in and there are a few other changes in too.
I'm trying to create different view controller to display different text on the final DetailView but i don't know how to begin.. A tutorial in this series would be very helpful, thanx a lot!
Daniel
Hi, Thanx for your tutorial and I like to adding Custom UITableViewCell (include UIImageView and Lable) for UITableView, How can I do?
Can you post here the sample please? Thanx!
Thanks for the great article. I need to add different individual tables instead of the detail view and they need to have data in 3 sections. Should I create individual nib files for each last view or is there a more efficient way since I am going to have 600 different table views at the end using combinations of 1st main categories and 2nd level sub categories for each. Any suggestions as to what approach should I use?
Will it be better to source the data from different plists into the same nib file everytime?
Twimfy said... "You show how to load data into the detail view by sending the text from the original array to the label..."
What did I miss? I want to load text from the plist/array into a label, but I don't see any dynamically fed labels in any detail view...?
However, RootViewController.xib has a pair of labels that contain static text: 'View One' & 'View Two' (hardwired via IB).
How to add another field/string onto each child in the plist and then use that to populate a label in a detail view (displayed as usual when the plist children hit their limit)?
The idea is to be able to pass one or two short summary paragraphs, related to a given child, into a final/detail view...as it is, passing a title only means anything more than a few short words are truncated due to them being displayed in a cell. A label, displayed at the end of each drilldown/tree/detail view, and fed from the plist, would allow for additional dynamic content w/word-wrap etc. Keeping this summary text in a plist makes the project more reusable by only needing to change Data.plist. At least that's how it works in my imagination :)
Thanks again and keep 'em coming.
Is there a way to load the Data.plist file from the Internet? And then have a refresh Button to reload the table and display the new data if the plist is modified.
This is great.
I'm retrieving all of my data from a remote server (using JSON / PHP / MySQL). Can you give a small example of how one would populate the data in the UITableView from a remote server (not SQLLite or a static aray) within the workflow outlined in the tutorial.
I am able to populate my UITableView with remote data but I am having problems performing the drill down.
Thanks for your time.
How would you approach this if you wanted an TabBarController overlayed on top of all your Tableviews?
I don't understand how can the new allocated RootViewController can still have reference to the previous value of CurrentLevel? Is this iPhone SDK only?
Every time an instance of RootViewController is allocated/created, its CurrentLevel will always start with 0, right? Please enlighten me
@Anonymous
Good Question. Yes, you are right everynew instance of the root view controller will have its own set of variables so the "CurrentLevel" will always start from zero. However, I do change the "CurrentLevel" of the root view controller before it is pushed at the top of the stack; this happens in tableView:didSelectRowAtIndexPath.
Hope this helps.
Happy Programming,
iPhone SDK Articles
Post a Comment