Add headers and footers to tableview sections

In the previous post, we saw how to add header and footer views to entire table view. In this post, we will see how to add header and footers to individual UITableView sections

Before we get started, make sure (Either programmatically or through storyboard) to add UITableView instance to your main view and pin all the edges to the superView.

Next, we will create the MyHeaderView and MyFooterView to act as header and footer of given TableView section respectively. They both are subclass of UITableViewHeaderFooterView since following method,

tableView.dequeueReusableHeaderFooterView(withIdentifier: <identifier>)

returns the instance which is UITableViewHeaderFooterView subclass.

class MyHeaderView: UITableViewHeaderFooterView {

    let label = UILabel(frame: .zero)

    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)

        let customBackgroundView = UIView(frame: .zero)
        customBackgroundView.translatesAutoresizingMaskIntoConstraints = false
        customBackgroundView.backgroundColor = .green

        contentView.addSubview(customBackgroundView)
        customBackgroundView.addSubview(label)

        label.textColor = .blue
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 0
        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalTo: customBackgroundView.leadingAnchor, constant: 16.0),
            label.trailingAnchor.constraint(equalTo: customBackgroundView.trailingAnchor, constant: -16.0),
            label.topAnchor.constraint(equalTo: customBackgroundView.topAnchor),
            label.bottomAnchor.constraint(equalTo: customBackgroundView.bottomAnchor),

            customBackgroundView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            customBackgroundView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            customBackgroundView.topAnchor.constraint(equalTo: contentView.topAnchor),
            customBackgroundView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            ])
    }

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

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Please note following things,

  1. We are using customBackgroundView to color given header or footer view since iOS no longer allow us to directly set backgroundColor property on UITableViewHeaderFooterView subclass
  2. We will utilize reuseIdentifier to save precious memory in case there are many sections and header/footer view may potentially be re-used
  3. We will set the background color of header view to green and text to blue to be able to distinguish header from rest of the content
  4. MyFooterView is created the same way. Only distinguishing feature - It's with the background orange and text color of black

Next we register both MyHeaderView and MyFooterView to tableView in our viewDidLoad method.

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.register(MyHeaderView.self, forHeaderFooterViewReuseIdentifier: "header")
    
    tableView.register(MyFooterView.self, forHeaderFooterViewReuseIdentifier: "footer")
}

Now assuming you've already registered cells in tableView, implement following dataSource methods to make tableView show some cells and section,

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 3
}

func numberOfSections(in tableView: UITableView) -> Int {
    return 4
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    cell.textLabel?.text = "Row \(indexPath.row) Section \(indexPath.section)"
    return cell
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 44.0
}

Now is the time to set up UITableViewDelegate methods to properly display header and footer views for each section,

We will implemented following methods to pick heights for header and footer,

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return UITableView.automaticDimension
}

func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
    return 44.0
}

func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
    return UITableView.automaticDimension
}

func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
    return 44.0
}

Please note that we are using UITableView.automaticDimension for height and value of 44.0 as an estimated height. This is because we want both header and footers to support dynamic sized text where they can grow as text content or size increases.

Now we are entering the final stage. Here we will dequeue cells with identifiers that were previously used to register header and footer view, configure them with arbitrary string and return that view.

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? MyHeaderView
    header?.configure(text: "Header for Section \(section)\nSomething\nasdasdad")
    return header
}

func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
    let footer = tableView.dequeueReusableHeaderFooterView(withIdentifier: "footer") as? MyFooterView
    footer?.configure(text: "Footer for Section \(section)\nSomething\nasdasdad")
    return footer
}
I have seen some tutorials where author simply resorts to creating new views inside of  viewForHeaderInSection or viewForFooterInSection methods. This is not an optimal solution. If you've many sections in the tableView, you are essentially creating new view  every time to act as a header or footer rather than re-using existing ones

So after all our efforts, our tableView is ready with rows, sections - And most importantly header and footers for each of those sections

And that is all I had to write about header and footer views in TableViews. If you haven't already checked, you can refer to my other post which talks about adding header and footer views to entire tableView.

Questions? Please reach out to me on Twitter!

References:

NSHipster.com