We’ve all done it, admit it, we’ve all written enormous if structures in the datasource/delegate methods of a table. It got the job done, but ever needed to change the order of the sections or needed to add/remove some rows? Things get out of hand very easily when the appearance and behavior of rows is varying. Indeed, these if structures are horrible maintenance-wise. Granted, tutorials often focus on showing some specific functionality and leave the refactoring to the reader, so aspiring iPhone developers often don’t know any better.
Here’s a way to keep your UITableViewController (or any other view controller or class your using as datasource or delegate) clean, this approach is aimed at tables with multiple sections. Tables with 1 section have limited benefit of using this approach although extra sections could be easily added later. The key is to move all the logic for a specific section in a table to its own class, which is responsible for his own rows and nothing more.
To achieve this we create a protocol called YCSectionController:
@protocol YCSectionController @required - (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section; - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; @optional - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section; @end
This protocol will be used to specify to which methods a custom section controller must conform. Each custom section controller has knowledge about the amount of rows & how to display each row, these are the required methods. Extra methods can be added like what to do when a row is selected for instance. Depending on the appearance of the section & the desired behavior each custom section controller class implements the necessary methods.
The YCHeaderSectionController implementation below shows a minimal implementation.
@implementation YCHeaderSectionController #pragma mark YCSectionController methods - (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section { return 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString *cellIdentifier = @"HeaderCell"; UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } if (indexPath.row == 0) { cell.textLabel.text = @"Yannick Compernol"; } return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { NSURL *url = nil; if (indexPath.row == 0) { url = [NSURL URLWithString:@"http://yannick.compernol.be"]; } if (url) { [[UIApplication sharedApplication] openURL:url]; } } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return @"Info"; } @end
One you’ve made several custom section controllers all you have to is to hook them up in your UITableViewController and dispatch the datasource/delegate methods to the custom section controllers. Note the UITableViewControllers has an array which will hold all the custom section controllers, the array is filled in the ViewDidLoad: method.
@interface MainTableViewController : UITableViewController { NSArray *sectionControllers; } @end @implementation MainTableViewController ... #pragma mark UIViewController methods - (void)viewDidLoad { [super viewDidLoad]; id header = [[YCHeaderSectionController alloc] init]; id content = [[YCContentSectionController alloc] init]; id footer = [[YCFooterSectionController alloc] init]; sectionControllers = [[NSArray alloc] initWithObjects:header, content, footer, nil]; [header release]; [content release]; [footer release]; } ... #pragma mark UITableViewDataSource methods - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [sectionControllers count]; } - (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section { id<YCSectionController> sectionController = [sectionControllers objectAtIndex:section]; return [sectionController tableView:table numberOfRowsInSection:section]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { id<YCSectionController> sectionController = [sectionControllers objectAtIndex:indexPath.section]; return [sectionController tableView:tableView cellForRowAtIndexPath:indexPath]; } ... #pragma mark UITableViewDelegate methods - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { id<YCSectionController> sectionController = [sectionControllers objectAtIndex:indexPath.section]; if ([sectionController respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) { [sectionController tableView:tableView didSelectRowAtIndexPath:indexPath]; } } @end
And that is about it, it can be taken further than I showed here, but I think this is a decent starter. The bottom line is that the UITableViewController has less code and the specific code (appearance & behavior) for each section is contained in its own class. Adding to the maintainability of the whole table.
A running example (screenshot shown above) can be downloaded here.
There’s only one thing I’m still struggling with, since id
