I’ve been experimenting a fair bit lately with custom table section header views (everything in this post applies equally to footers I expect). The core functionality is pretty clear, you need to implement this UITableViewDelegate method:
- Available in iOS 2.0 and later.
Seems simple enough. As you may have noticed, I’m a storyboard kinda guy. Unfortunately there’s no support in the Storyboard Editor for defining custom section headers (rdar://17840343), so let’s try out this juicy looking bit of API instead:
OK, no problem; lay out the xib, and then go ahead and register it in ‑viewDidLoad. And that’s where the problems begin:
*** Assertion failure in -[UITableView _dequeueReusableViewOfType:withIdentifier:], /SourceCache/UIKit_Sim/UIKit-2935.137/UITableView.m:5413
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'invalid nib registered for identifier (header) - nib must contain exactly one top level object which must be a UITableViewHeaderFooterView instance'
OK, not mentioned in the docs. Fine, request filed, moving on. Head back into IB and have a look for UITableViewHeaderFooterView.
This is where things start to get a bit weirder. If you take a glance at the UITableViewHeaderFooterView class, you’ll see it has a lot in common with brethren like UITableViewCell and UICollectionViewCell. There’s a content view for adding our own content to, background view for configuring the background, and some pre-defined text labels.
But unlike those other classes, there’s no pre-made instance in the IB palette. You can’t drag one out and configure it directly. Instead you need to use a regular UIView, assign it a custom class, and then add content directly to it. Ignore all those other views which UITableViewHeaderFooterView has thoughtfully pre-defined; they’re not available to you!
Working in this manner, we quite quickly have our custom header configured. One catch to watch out for: you can’t set the UITableViewHeaderFooterView’s background color directly; the table will clear it out at runtime. Add a subview and configure that instead.
That’s the full-on xib/nib path out of the way. The alternative is to do it purely in code.
For simple modifications, this works pretty cleanly. Ideally, register the class so as to get nice view re-use. When asked for the header view, you don’t even need to worry about assigning a value to textLabel; the table takes care of doing that from your implementation of -tableView:titleForHeaderInSection:
My experimentation says this is fine for things like tweaking the default colours used. But for anything more advanced you run out of luck. I tried to create a view inset slightly within the existing background view and the table refused to auto re-size it correctly.
Adjusting text label layout is much the same. Any attempt to reposition the label will promptly be undone by UITableViewHeaderFooterView’s own layout logic (I wanted to try insetting the text a little more).
Sensibly the next thing to try then is a subclass. There, things get even weirder. If you try to override ‑layoutSubviews to make your own adjustments, the result is an infinite loop. Weirdly it seems the act of changing textLabel’s position (I tried both frame and center) triggers another layout pass.
So at this point I’d say we’re back to square one. If you need any serious customisation, all the views are going to have to be created, configured and laid out programatically. And so in my view you might as well go for using a xib as detailed above.