How to add HeaderView and FooterView to UITableView

Recently I was playing with support to add header and footer view to UITableView. However, task wasn't as easy as adding UITableViewCells to the table view. Header and Footer views acts slightly different than regular table view cells. Today we are going to see how to add self-sizing (self-sizing is important) header and footer view to the entire tableView or individual sections.

We're going to learn following things in today's tutorial,

  1. Adding Header View to the entire TableView
  2. Adding Footer View to the entire TableView
Better, we're not just going to add simple header or footer view, but we will make it self-sizing so that they can fit to any content possible

First, let's start with a simple UITableView in the storyboard, pin it to all edges of its superview and connect IBOutlet inside the related UIViewController subclass.

Please also make sure that we're adding UITableViewCell with the reuse identifier "cell" inside this TableView
  1. Adding Header View to the entire TableView

Since this is the header view for entire table view, we won't need any kind of reuse identifier since it will appear just once.

Let's start by creating a UIView subclass with single label which will act as a HeaderView. We expect this UILabel to be multiline to demonstrate our ability to support self-sizing table view headers.

class Component: UIView {

    let label = UILabel(frame: .zero)

    override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 0
        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0),
            label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0),
            label.topAnchor.constraint(equalTo: topAnchor),
            label.bottomAnchor.constraint(equalTo: bottomAnchor),
            ])
    }

    func configure(text: String) {
        label.text = text
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
We are going to use the same component to act as a Header and Footer view for the same TableView

Next, inside your viewDidLoad method add following piece of code to instantiate Component and assign it as a header view

override func viewDidLoad() {
    super.viewDidLoad()
    let headerView = Component(frame: .zero)
    headerView.configure(text: "I am the main header view\nI am second line\nHell yeah")
    tableView.tableHeaderView = headerView
    tableView.tableHeaderView?.backgroundColor = .red
}

Please note few things,

  1. We're instantiating Component with zero frame. This is because we don't know how long the label text will grow
  2. We have a configure method on the component which allows us to set any arbitrary text to the UILabel instance
  3. We are coloring tableHeaderView with red background color for easier identification

If we run the app now, we will not see any header.

This is because even though our Component is set to grow with label, tableHeaderView associated with tableView does not support autolayout and will not grow as label grows.

In order to tackle it, we have to wait until tableHeaderView is fully laid out, grab the systemLayoutSizeFitting, and get estimated height of label for the given width. We will add utility method to manually adjust the tableHeaderView height and call it from viewWillLayoutSubviews

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    updateHeaderViewHeight(for: tableView.tableHeaderView)
}

func updateHeaderViewHeight(for header: UIView?) {
    guard let header = header else { return }
    header.frame.size.height = header.systemLayoutSizeFitting(CGSize(width: view.bounds.width - 32.0, height: 0)).height
}
Please note how we are subtracting 32.0 pixels from available width. This is because the label is provided with padding of 16.0 pixels on both sides

Now if you run the app it will look like this,

2. Adding Footer View to the entire TableView

Now, if you want to add a footer at the bottom, you can follow very similar procedure. Just add the following code in overridden viewDidLoad method

let footerView = Component(frame: .zero)
footerView.configure(text: "I am the main footer view\nThis is second line\nYes, right")
tableView.tableFooterView = footerView
tableView.tableFooterView?.backgroundColor = .yellow

Now, we also want footerView to adjust as auto layout lays out rest of the views and update the height from width of the given view so we will just make a call to updateHeaderViewHeight after viewWillLayoutSubviews gets called.

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    updateHeaderViewHeight(for: tableView.tableHeaderView)
    updateHeaderViewHeight(for: tableView.tableFooterView)
}

And we're done with setting flexible height headerView and footerView to our tableView. This is how our completed work looks like,

In the next post we will see how to add header and footer to individual sections in the given TableView.
Update: New post is done and live. You can refer to it here which talks about adding header and footer views to individual tableView sections