iOS
UITableView
HeaderView
iOS Development
Swift Programming

Adding iOS UITableView HeaderView not section header

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

UITableView supports two different header concepts, and mixing them up causes a lot of confusion. A table header view sits once at the top of the entire table, while a section header belongs to a specific section and is managed through delegate methods.

Table Header View Versus Section Header

If you want one banner, profile block, search area, or summary card above all rows, use tableHeaderView. Do not implement tableView(_:viewForHeaderInSection:) unless the header belongs to a particular section.

The table-level header is attached directly to the table:

swift
tableView.tableHeaderView = headerView

That is the entire API surface, which is why the feature looks deceptively simple. Most of the real work is sizing the header correctly.

Add a Simple Header Programmatically

For a fixed-height header, you can create a view, assign a frame, and attach it.

swift
1import UIKit
2
3final class UsersViewController: UIViewController {
4    @IBOutlet private weak var tableView: UITableView!
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8
9        let header = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 120))
10        header.backgroundColor = .systemBlue
11
12        let titleLabel = UILabel(frame: CGRect(x: 16, y: 16, width: tableView.bounds.width - 32, height: 32))
13        titleLabel.text = "Team Directory"
14        titleLabel.font = .boldSystemFont(ofSize: 24)
15        titleLabel.textColor = .white
16
17        header.addSubview(titleLabel)
18        tableView.tableHeaderView = header
19    }
20}

That is enough for simple static headers. Unlike reusable section headers, a table header is just an ordinary view instance.

Auto Layout Needs an Extra Step

The most common surprise is that tableHeaderView does not automatically resize itself from constraints the same way cells do. If the header uses Auto Layout, you usually need to calculate the fitted height, update the frame, and assign the header back to the table.

swift
1import UIKit
2
3final class UsersViewController: UIViewController {
4    @IBOutlet private weak var tableView: UITableView!
5    @IBOutlet private weak var headerContainerView: UIView!
6
7    override func viewDidLoad() {
8        super.viewDidLoad()
9        tableView.tableHeaderView = headerContainerView
10        resizeHeader()
11    }
12
13    override func viewDidLayoutSubviews() {
14        super.viewDidLayoutSubviews()
15        resizeHeader()
16    }
17
18    private func resizeHeader() {
19        guard let header = tableView.tableHeaderView else { return }
20
21        header.setNeedsLayout()
22        header.layoutIfNeeded()
23
24        let targetSize = CGSize(width: tableView.bounds.width, height: UIView.layoutFittingCompressedSize.height)
25        let height = header.systemLayoutSizeFitting(targetSize).height
26
27        if header.frame.height != height {
28            header.frame.size.height = height
29            tableView.tableHeaderView = header
30        }
31    }
32}

Reassigning tableView.tableHeaderView = header after changing the frame is important. Without that, the table often does not pick up the new height.

Loading a Header from a XIB

If the header is substantial, a xib-backed view is often cleaner than building it inline.

swift
1import UIKit
2
3final class ProfileHeaderView: UIView {
4    @IBOutlet private weak var nameLabel: UILabel!
5
6    func configure(name: String) {
7        nameLabel.text = name
8    }
9}

Then load and attach it:

swift
1let nib = UINib(nibName: "ProfileHeaderView", bundle: nil)
2let header = nib.instantiate(withOwner: nil, options: nil).first as! ProfileHeaderView
3header.configure(name: "Ada Lovelace")
4tableView.tableHeaderView = header

You still need proper sizing logic if the height is content-driven.

When a Section Header Is the Better Tool

Use a section header when the content repeats per section or should scroll and pin according to section behavior. Use tableHeaderView when the header is a single top-level decoration or summary for the entire table.

That distinction matters because table headers are not reused and are not part of the section-delegate lifecycle.

Common Pitfalls

The biggest mistake is implementing a section header when the UI actually needs a table header. That creates repeated or misplaced views.

Another common issue is expecting Auto Layout alone to size tableHeaderView. Unlike cells, table headers usually need explicit frame adjustment.

Developers also change the header's height but forget to assign it back to tableView.tableHeaderView. The frame changes in memory, but the table does not refresh its layout.

Finally, do not assume a table header is reusable like UITableViewHeaderFooterView. It is a single attached view, so manage it like a normal view object.

Summary

  • Use tableView.tableHeaderView for one header above the entire table.
  • Use section headers only for per-section content.
  • Fixed-size headers are simple; Auto Layout headers need explicit height calculation.
  • After resizing a table header, assign it back to tableView.tableHeaderView.
  • Table headers are ordinary views, not reusable section header objects.

Course illustration
Course illustration

All Rights Reserved.