Hello UITableView!

Ok, let’s start with something really simple. We’ll be creating an app to keep track of your DVD library. The app will have a table view, a detail view and some basic navigational components.

Pre-requisites

I assume you have Xcode and iPhone SDK 3.x already installed (I’m using version Xcode 3.1, with 3.2 being the newest at the time of this writing). I also assume you know how to create a project so I’m not going to cover those little things here. Lastly, I assume you have some knowledge Objective-C, its principles, syntax and paradigms.

1. Set up your project

Create a “Navigation-based Application” and name it “TableViewApp.” Right off the bat, you’ll have an empty app, which is pretty much useless. You can run it using Cmd+R.

2. Create a dummy data set

Right-click on the Resources folder and choose Add -> New File. Under Other category, choose Property List. Name it TestData.plist. This is basically an XML file that will have an empty Dictionary in it by default. Change Dictionary to Array since we’re going to be adding numerous “DVDs” which will be described as individual Dictionaries.

tutorial001

You can add data to it by simply clicking the little button to the right of the Value column. We will be adding information about your DVD collection to it so the schema of your data set could be something like this:

  • Title – String
  • Cover image – String
  • Feature length  - Number (in minutes)
  • Release date – Date

Of course, this could be extended to more fields such as director, genre, aspect ratio, etc.

Following this schema, add a few items to your dummy data set. You’ll eventually want to have your file looking something like this:

tutorial002

If you don’t feel like typing all this info in, you can download the file here: TestData.plist.

3. Create a DAO (Data Access Object) to access your data

Right-click on the Classes folder and choose Add -> New File. Under Cocoa Touch Class category choose Objective-C class. Name it DvdLibraryDao.

This will be a very simple class that will take our dummy data file, read it in as a NSArray and provide some utility methods to access the data from the file.

First, let’s define our custom init method and some instance variables that will be needed for our implementation.

// Gets a plist name and reads in its contents as an array
@interface DvdLibraryDao : NSObject {
    NSString *libraryPlist;
    NSArray *libraryContent;
}
 
@property (nonatomic, readonly) NSString *libraryPlist;
@property (nonatomic, readonly) NSArray *libraryContent;
 
- (id)initWithLibraryName:(NSString *)libraryName;
- (NSDictionary *)libraryItemAtIndex:(int)index;
- (int)libraryCount;
 
@end

Next we create a custom initialization method, which will give us the name of the property list we want to read in. This code goes in your DvdLibraryDao.m file.

// Gets a plist name and reads in its contents as an array
 - (id)initWithLibraryName:(NSString *)libraryName {
    if (self = [super init]) {
        libraryPlist = libraryName;
        libraryContent = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle]
                                                                  pathForResource:libraryPlist ofType:@"plist"]];
    }
    return self;
}

To retrieve information about a particular DVD, we’ll want a method that would access our array at a requested index and then returns the NSDictionary that contains the data we’re after. Add this method to DvdLibraryDao.m.

// [Safely] returns a "DVD" from the plist. Each item in the data source is a dictionary containing data about
// the chosen DVD.
- (NSDictionary *)libraryItemAtIndex:(int)index {
    return (libraryContent != nil && [libraryContent count] > 0 && index < [libraryContent count])
        ? [libraryContent objectAtIndex:index]
        : nil;
}

The last method simply returns the count of DVDs in our data file.

// Returns the count of all DVDs in our library
- (int)libraryCount {
    return (libraryContent != nil) ? [libraryContent count] : 0;
}

4. Make your Table View read from the DAO

Now that we have a data source, let’s make our table read from it.

Add an instance variable to your RootViewController.h called dao of type DvdLibraryDao.

#import "DvdLibraryDao.h"
 
@interface RootViewController : UITableViewController {
    DvdLibraryDao *dao;
}
 
@end

We’ll initialize this variable in the viewWillAppear method that can be found in the implementation file of RootViewController.

- (void)viewWillAppear:(BOOL)animated {
    dao = [[DvdLibraryDao alloc] initWithLibraryName:@"TestData"];
 
}

TestData here is the name of the plist containing our data we created earlier.

To tell our table how many rows it has, we override the delegate method of UITableView called numberOfRowsInSection. All this method does is it returns the number of rows there are in the current section. We’ll cover sections later.

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [dao libraryCount];
}

We’re calling the libraryCount method we’ve implemented earlier to get the number of DVDs in our library.

In the last step before we can run this project and see our records in the table, we tell the table what to display at each row. We do this in the delegate method cellForRowAtIndexPath.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"LibraryListingCell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:CellIdentifier] autorelease];
    }
 
	cell.textLabel.text = [[dao libraryItemAtIndex:indexPath.row] valueForKey:@"title"];
 
    return cell;
}

One note about line #4 that might not be so obvious  - static NSString *CellIdentifier = @”LibraryListingCell”. Since tables can potentially have thousands of rows, the system memory consumed by creating all the rows at once would in the best case considerably slow down your table’s performance and in the worst case crash your application.

That’s why Apple introduced the concept of cell reuse. Behind the scenes, the system only creates the visible cells (and some) and as you start scrolling, it reuses the cells that are currently off screen to display the content that is on screen. By giving a cell an identifier, you’re basically marking it to be reused over and over again in your view instead of continuously creating new cells.

5. Add a title to the header

To add a title to the header of the table view, we simply add this line to the viewWillAppear method we overrode earlier.

    self.title = @"My DVD Library";

6. See what it looks like so far

Run your project (in the simulator or on the device) and if everything is wired correctly, you should see something like this:

tutorial003

7. Create the Detail View

After a user selects one of the rows, we want them to go to a detail page where more information about the DVD is shown. We could simply use a UIView and add a bunch of UILables to it to achieve this but since we’re covering UITableView in this tutorial, we’ll create another table.

(Note that this NOT how you’d want do it in a real-life app. I’m showing it strictly to demonstrate a grouped style table.)

Right-click on the Classes folder and choose Add -> New File again.  From Cocoa Touch Class category pick the Objective-C class and then choose UITableViewController from the subclass dropdown menu in the lower half of the window. This is different in Xcode 3.1 from previous versions where UITableViewController was simply a part of the presented classes. Name the file DetailViewController.

tutorial004

There are few things we need to do here. We’ll need some place to hold values of the labels used to describe our fields and also the actual values of the fields. For that, let’s create two instance variables of type NSMutableArray which we’ll later fill with appropriate data.

To properly initialize our data in the detail view, we’ll also need a custom init method. It is a modified version of initWithStyle that comes by default with UITableViewController.

@interface DetailViewController : UITableViewController {
    NSMutableArray *labels;
    NSMutableArray *values;
}
 
- (id)initWithStyle:(UITableViewStyle)style andDvdData:(NSDictionary *)dvdData;
 
@end

Let’s implement our custom init method:

- (id)initWithStyle:(UITableViewStyle)style andDvdData:(NSDictionary *)dvdData {
    if (self = [super initWithStyle:style]) {
        labels = [NSMutableArray new];
        values = [NSMutableArray new];
 
        // Set up labels which will become header titles
        [labels addObject:@"Title"];
        [labels addObject:@"Release Date"];
        [labels addObject:@"Length"];
 
        // Add now the values that correspond to each label
        [values addObject:[dvdData valueForKey:@"title"]];
        // We're faking formatting of date and length here simply dumping them as objects.
        // We should really convert the values to proper data types and then display them as strings.
        [values addObject:[NSString stringWithFormat:@"%@", [dvdData valueForKey:@"releaseDate"]]];
        [values addObject:[NSString stringWithFormat:@"%@ minutes", [dvdData valueForKey:@"featureLength"]]];
    }
    return self;
}

Each table header will display the label of our field (e.g.Title or Release Date). To do that, we’ll override UITableView’s delegate method titleForHeaderInSection.

- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section {
	return [labels objectAtIndex:section];
}

Now we need to handle header and row counts as well as displaying actual values in our table rows.

The number of headers would correspond to the number of labels we want to display.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [labels count];
}

We’re going to cheat a little bit here. As I mentioned previously, a table view isn’t exactly the best choice to display this kind of data. The reason we’re going to return 1 as number of rows per section is because if we set it to the count of values, we would get duplicate values per each section. Instead of leaving the value as 1, try setting it to [values count] and see what I mean.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
}

Finally, we set the cell textual value to each corresponding field.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"DetailViewCell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:CellIdentifier] autorelease];
    }
 
    cell.textLabel.text = [values objectAtIndex:indexPath.section];
 
    return cell;
}

8. Modify RootViewController to handle DetailViewController

As the last step, we’ll need to tell RootViewController to actually display DetailViewController when a row is pressed. We do that in the didSelectRowAtIndexPath delegate method.

// Override to support row selection in the table view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // UITableViewStyleGrouped table view style will cause the table have a textured background
    // and each section will be separated from the other ones.
    DetailViewController *controller = [[DetailViewController alloc]
                                        initWithStyle:UITableViewStyleGrouped
                                        andDvdData:[dao libraryItemAtIndex:indexPath.row]];
    controller.title = [[dao libraryItemAtIndex:indexPath.row] valueForKey:@"title"];
    [self.navigationController pushViewController:controller animated:YES];
    [controller release];
}

One more detail. When we return back to RootViewController from DetailViewController, we’ll want to deselect the row that had been pressed. To do so, we modify the viewWillAppear method.

- (void)viewWillAppear:(BOOL)animated {
    dao = [[DvdLibraryDao alloc] initWithLibraryName:@"TestData"];
    self.title = @"My DVD Library";
    [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}

If everything went well, re-running the project and tapping on one of the rows should show you the detail view table that should look like this:

tutorial005

Conclusion

Hopefully this tutorial has given you a good starting point for creating your own app. The next steps could be replacing the static plist file with a Core Data store or maybe pull the data from a remote XML file. Also, the DetailViewController should really be a subclass of UIView that would provide a lot more flexibility in laying out your data.

You can download a finished version of this project here: My DVD Library Xcode Project.


29 Responses to “Hello UITableView!”

Copyright © 2009 Vladimir Olexa | Copyright © 2009 Apple Inc. | Powered by WordPress