Friday, May 30, 2014

UITableViewHeader done right

There are basically three possibilities to create headers in UITableViews
  1. Create them in the Code
  2. Prepare them as UITableViewCell in your storyboard and use them in the code
  3. Put them in separate .xib files and register these files as prototypes for headers or footers
(More details can be found here)

1) Create them in Code means that you'll need to define a UIView class and add subviews to that until you'll have the header you wanna see. If you don't use storyboard and don't want to use any additional .xib files this might be the solution. However this means that you'll need to define manually all constraints which might be quite painful if your header is complex in structure

2) The second method looks quite simple and straight forward (Described here more in detail), however it has some drawbacks. Actually in this case you're returning a Cell that you dequeued from the pool of cells the table holds internally. From iOS 7.1 on you'll get a warning in the log that you're accessing a cell without index path. Furthermore there is another issue causing this cells to disappear on partial reload of sections.
Obviously that's not acceptable in productive code, so the solution can be to return the "cell.contentView" instead of the cell itself. This is working and the results where really promising until I did a "longpress" on such a header. This action will cause the header to send a message to its cell saying that the gesture recognizer should begin to work. As the cell is not there anymore this will lead to a crash of the whole APP (BAD_ACCESS_ERR)
To fix this you'll need to remove all gesture recognizers from the contentView before returning it as header view as this stackoverflow answer points out.

3) This is actually the solution if you want to use xcodes graphical interface builder tools and the process is actually quite simple.
First create a class that inherits from UITableViewHeaderFooterView, then create a .xib file containing this view (I usually put the TableViewController as file owner ) as class with the complex header fields you need.
Next open your TableViewController and register this .xib as prototype for the header.
- (void)viewDidLoad {
    [super viewDidLoad];
    UINib *sectionHeaderNib = [UINib nibWithNibName:@"SectionHeaderView" bundle:nil];
    [self.tableView registerNib:sectionHeaderNib forHeaderFooterViewReuseIdentifier:SectionHeaderViewIdentifier];
}
In the viewForHeaderInSection method you can now dequeue it and use it
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    SectionHeaderView *sectionHeaderView = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:SectionHeaderViewIdentifier];
    [..Fill the view with real values..]
    return sectionHeaderView;
}
For more details to this last method watch the complete sample program