How to size a table header view using Auto Layout in Interface Builder

If you’ve tried to create a table header view in Interface Builder, you may have noticed that it doesn’t size properly even when you have all the right constraints. Here’s what happens if you just set up your constraints:

Incorrectly sized table header view

Even though all the constraints are set properly on the table header view, its height is clearly wrong since we want to show the full text in the label. And yes, its numberOfLines is already set to 0. So what can we do?

The solution

The solution is pretty simple, even if it isn’t so easy to discover. We’ll start from the very beginning, but if you’re confident with your constraints and connections already, skip ahead to Step 9.

1. Add a table view to your view in Interface Builder

Drag a table view onto your view and position it so it fills the screen. Nothing special here.

2. Set the constraints on the table view

Add constraints to the table view so it’s pinned to each edge of the screen (making sure, as always, to uncheck “Constrain to margins” first) – so it’ll stretch to fill the full screen on any device.

3. Connect the table view to the ViewController

Connect the table view from the Storyboard to your ViewController as a new IBOutlet.

4. Add a table header view

To add a table header view, grab a view and drop it near the top of your table view. (You can create your header view in a XIB if you prefer – the problem and solution are the same.)

5. Drop a UILabel onto the table header view

Grab a label from the object library and drop it onto the header view. Use the guides to position it 8 points from each edge of the table view. Make sure to drop some long text into your label, too.

Like so:

IB - Table view

6. Set the constraints on the table header view

Use the Pin tool to set the label 8 points from each edge of its superview.

7. Create a CustomHeaderView class for the table header view

Create a CustomHeaderView class that subclasses UIView. In the Storyboard (or XIB), set your table header view’s class to CustomHeaderView.

8. Connect the label to the CustomHeaderView with a new IBOutlet

Create a new IBOutlet in your CustomHeaderView class:

@IBOutlet var label: UILabel!

Then click and drag to connect it to the label in Interface Builder.

9. Implement layoutSubviews() in CustomHeaderView and set the label’s preferredMaxLayoutWidth

This one of the major pieces – we need to make sure the preferredMaxLayoutWidth is set properly on the label. This is what allows the label to grow or shrink to the proper height with Auto Layout. I’m not sure why this doesn’t get set automatically like it should on iOS 8, but labels don’t seem to size properly in table views unless you set the preferredMaxLayoutWidth explicitly. Luckily, we can get the proper width to use in layoutSubviews().

Override layoutSubviews() in CustomHeaderView, making sure to call super, and set the preferredMaxLayoutWidth on your label:

override func layoutSubviews() {
    super.layoutSubviews()
    label.preferredMaxLayoutWidth = label.bounds.width
}

Notice how we’re not doing any calculations or anything special – the label’s bounds are already set for us, so we just need to set the preferredMaxLayoutWidth to the label’s current width in layoutSubviews().

10. In the ViewController’s viewDidLayoutSubviews(), resize the header view

Now we need to resize the header view to allow all of the label’s text to fit inside it.

(Update: Thanks to Istvan Balogh for pointing out that this should be done in viewDidLayoutSubviews() – not viewDidAppear(_:) like I originally suggested.)

Here’s how I did it in my ViewController:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    sizeHeaderToFit()
}

func sizeHeaderToFit() {
    let headerView = tableView.tableHeaderView!

    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()

    let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    var frame = headerView.frame
    frame.size.height = height
    headerView.frame = frame

    tableView.tableHeaderView = headerView
}

Again, we’re not doing any calculations or anything special – we’re letting Auto Layout tell us the height that the headerView should be using systemLayoutSizeFittingSize(_:). And then we’re taking that height value and setting it on the headerView frame. And finally, we’re setting that headerView as the tableHeaderView at the very end to make sure the tableView has the updated headerView with the proper frame.

Et voilà – it works!

Correctly sized header view

Get the code on GitHub.