by Alex Rozanski

Simple table views in iOS

Mar 29th 2011Published in Development

UITableView is arguably one of the most important UI classes in UIKit, for its use in so many interfaces, in fitting with the dominant list-based UI paradigm in iOS.

The way UITableView is constructed makes it really well suited to displaying dynamic data, through the use of the delegation pattern. The control caters really well to data changes, by not storing the data that is to be displayed, but rather asking for it when it is needed.

The problem with UITableView really lies in displaying static content – content that largely doesn’t change, and content which is known to you at development time. Good examples of this would be any form of navigation, such as the hierarchical navigation structure in Settings.app, or an interface that is fixed, such as the detail view for a single contact in Contacts.app.

For these kind of interfaces, although you may want the table view look and feel, implementing the delegate/data source methods often feels very verbose for how you are using it.

I really like the article Fraser Speirs has written to counter the problems associated with UITableView in such a scenario. The idea is essentially to create enumerations for the sections in the table view, and the rows in each section. This provides constants as mappings between row and section indexes, and the data you want to display. What’s more is that each enumeration is ended with a constant which can be used for the number of sections/rows in that enumeration, which works on the fact that the enumeration is 0-based. Here is an example:

enum Sections {
     kHeaderSection = 0,
     kTitleSection,
     kAuthorSection,
     kBodySection,
     NUM_SECTIONS
};

Now you can return NUM_SECTIONS in-numberOfSectionsInTableView:, which will always be the number of sections in the enumeration, and you can use switch statements in row or section-dependent methods such as -tableView:cellForRowAtIndexPath: with these constants, rather than having to hard-code row and section numbers.

Although much more resistant to future change and more structured, the problem with this method is that your code quickly devolves into a mess of switch statements, which doesn’t make the code much less verbose.

Solving the problem with PXSimpleTableAdapter

My primary motivation for PXSimpleTableAdapter was to overcome this problem of verbosity, and make setup and getting information into a table view really easy.

Why did I name it “Simple” table adapter? Because the classes are designed for only this use case – where you have “static” data. If you have dynamic data, UITableViewDelegate and UITableViewDataSource are the way to go.

Get the source

As with all of my open source projects, you can clone the project straight from GitHub:

git clone git://github.com/Perspx/PXSimpleTableAdapter.git

Or have a look at the project page at http://github.com/Perspx/PXSimpleTableAdapter.

Classes

The structure of the classes is simple:

  • PXSimpleTableAdapter: This is the class which transforms the data you pass it into cells that can be used by a UITableView, as well as handling selection.
  • PXSimpleTableSection: This class encapsulates information about a section in the table, including the rows it contains.
  • PXSimpleTableRow: This class encapsulates information about a row in the table, such as its title and icon.

Using PXSimpleTableAdapter is very straighforward:

  1. Lay out a UITableView in Interface Builder.
  2. Create an instance of PXSimpleTableAdapter in your view controller class and set the tableView property to your table view instance. This property is declared as an outlet so this step can also be done in IB, by dragging out an Object from the Object Library for your table adapter instance then hooking this up to your view controller as another outlet).
  3. Create an array of PXSimpleTableSection instances, and set each section’s array of PXSimpleTableRow instances.
  4. Pass the array of sections to -setSections: on the PXSimpleTableAdapter instance.

Convenience methods can be added if sections or rows need to be added or removed. Have a look at the PXSimpleTableSection and PXSimpleTableRow header files for more information.

Data Model

Each PXSimpleTableSection instance encapsulates:

  • The header title for the section
  • The footer title for the section
  • The array of rows for that section

Each PXSimpleTableRow instance encapsulates:

  • The title of the row
  • The icon for the row (if needed)
  • The accessory type (from the UITableViewAccessoryType enumeration)

Rows also have a selection block and an accessory tapped block property. These blocks are invoked when the row is selected, or their accessory button is tapped, respectively.

Example

Here is an example of creating a view which looks similar to that of Settings.app (if using a table view with the grouped style):

NSArray *firstSectionRows = [NSArray arrayWithObjects:[PXSimpleTableRow rowWithTitle:@"Sounds"],
                                                      [PXSimpleTableRow rowWithTitle:@"Brightness"],
                                                      [PXSimpleTableRow rowWithTitle:@"Wallpaper"], nil];

PXSimpleTableSection *firstSection = [[PXSimpleTableSection alloc] initWithSectionHeaderTitle:nil
                                                                           sectionFooterTitle:nil
                                                                                         rows:firstSectionRows];

NSArray *secondSectionRows = [NSArray arrayWithObjects:[PXSimpleTableRow rowWithTitle:@"General"],
                                                       [PXSimpleTableRow rowWithTitle:@"Mail, Contacts, Calendars"],
                                                       [PXSimpleTableRow rowWithTitle:@"Phone"], nil];

PXSimpleTableSection *secondSection = [[PXSimpleTableSection alloc] initWithSectionHeaderTitle:nil
                                                                            sectionFooterTitle:nil
                                                                                          rows:secondSectionRows];

NSArray *sections = [[NSArray alloc] initWithObjects:firstSection, secondSection, nil];
[firstSection release];
[secondSection release];

PXSimpleTableAdapter *tableAdapter = ...
[tableAdapter setSections:sections];
[sections release];

For such static content, this is much more manageable, and the code to set up the view resides in one method only, rather than 5 or so delegate messages with their own conditionals.

Custom Cells

By default, the data encapsulated in each PXSimpleTableRow is transformed to a UITableViewCell with the default style. This cell is then passed to the table view for display.

To use a custom cell in rows, you must subclass PXSimpleTableRow where you can provide your own cells and set them up as necessary. You must create one PXSimpleTableRow subclass for each type of cell you are going to be using.

There is more information on how to do this in the project README.

But what about delegation?

Delegation is one of my favourite patterns used in Cocoa and it really is powerful. The aim of this project is not to break delegation or suggest that this is the way UITableView should go.

However, the aim of PXSimpleTableAdapter is simply as a convenience for implementing this particular use case of table views in a simple manner.

Support/Bugs

If you have any bugs to report then you can do so on the GitHub project page.

If you have any queries about the project, first check out the project README on GitHub, or the Demo App bundled with the source. If your question still remains unanswered, you can contact me here. I’ll do my best to reply to any queries if I can.

Comments — 2

  1. John Topley

    Mar 29, 2011

    This looks really useful – thanks!

  2. Aaron

    Aug 10, 2011

    As an iOS newbie, this is just what I was looking for. Thanks!

Add comment

Please enter your name and comment