iOS Development
UITableView
Swift Programming
Mobile App Development
iOS UI Customization

Customize UITableView header section

Master System Design with Codemia

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

Introduction

Customizing a UITableView section header is straightforward once you decide how much control you need. For a plain label, the built-in header title works. For custom fonts, spacing, colors, buttons, or reusable layouts, return a custom view or a UITableViewHeaderFooterView subclass.

Start with the Simple Built-In Header

If the only requirement is a title, use tableView(_:titleForHeaderInSection:). It is the lightest option and works well for basic grouped lists.

swift
1import UIKit
2
3final class ItemsViewController: UITableViewController {
4    let sections = ["Favorites", "Others"]
5
6    override func numberOfSections(in tableView: UITableView) -> Int {
7        sections.count
8    }
9
10    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
11        sections[section]
12    }
13}

As soon as design requirements go beyond plain text, move to a custom header. The default title API gives very limited styling control.

Return a Custom Header View

For more control, implement tableView(_:viewForHeaderInSection:) and provide an explicit height.

swift
1import UIKit
2
3final class ItemsViewController: UITableViewController {
4    let sections = ["Favorites", "Others"]
5
6    override func numberOfSections(in tableView: UITableView) -> Int {
7        sections.count
8    }
9
10    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
11        let container = UIView()
12        container.backgroundColor = .secondarySystemBackground
13
14        let label = UILabel()
15        label.translatesAutoresizingMaskIntoConstraints = false
16        label.text = sections[section]
17        label.font = .preferredFont(forTextStyle: .headline)
18        label.textColor = .label
19
20        container.addSubview(label)
21
22        NSLayoutConstraint.activate([
23            label.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
24            label.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16),
25            label.topAnchor.constraint(equalTo: container.topAnchor, constant: 8),
26            label.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -8)
27        ])
28
29        return container
30    }
31
32    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
33        44
34    }
35}

This approach is fine for simple custom designs, but it can get repetitive if several sections share the same header layout.

Use UITableViewHeaderFooterView for Reuse

For more complex headers, subclass UITableViewHeaderFooterView. This gives you reuse behavior similar to table view cells and keeps header code out of the controller.

swift
1import UIKit
2
3final class SectionHeaderView: UITableViewHeaderFooterView {
4    static let reuseID = "SectionHeaderView"
5
6    private let titleLabel = UILabel()
7
8    override init(reuseIdentifier: String?) {
9        super.init(reuseIdentifier: reuseIdentifier)
10
11        contentView.backgroundColor = .systemGroupedBackground
12        titleLabel.translatesAutoresizingMaskIntoConstraints = false
13        titleLabel.font = .preferredFont(forTextStyle: .headline)
14        contentView.addSubview(titleLabel)
15
16        NSLayoutConstraint.activate([
17            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
18            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
19            titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
20        ])
21    }
22
23    required init?(coder: NSCoder) {
24        fatalError("init(coder:) has not been implemented")
25    }
26
27    func configure(title: String) {
28        titleLabel.text = title
29    }
30}

Register and dequeue it like this:

swift
1override func viewDidLoad() {
2    super.viewDidLoad()
3    tableView.register(SectionHeaderView.self, forHeaderFooterViewReuseIdentifier: SectionHeaderView.reuseID)
4}
5
6override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
7    guard let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: SectionHeaderView.reuseID) as? SectionHeaderView else {
8        return nil
9    }
10
11    header.configure(title: sections[section])
12    return header
13}

This is the better design when the header includes accessory buttons, dynamic state, or reusable styling.

Common Pitfalls

The most common mistake is using titleForHeaderInSection and expecting full control over fonts, spacing, or background styling. That API is intentionally limited.

Another common issue is returning a custom header view but forgetting to supply a matching height. If the table view gives the header too little space, the layout may appear clipped or broken.

Reuse is another source of bugs. If a reusable header contains buttons or stateful subviews, reset any old state when configuring it for a new section. Otherwise tap handlers, hidden flags, or old titles can leak across sections.

Finally, set colors on the correct view. UITableViewHeaderFooterView has both the header view itself and its contentView, and styling the wrong one can produce confusing results.

Summary

  • Use titleForHeaderInSection for plain text-only headers.
  • Use viewForHeaderInSection when you need custom layout or styling.
  • Set an explicit header height that matches the layout.
  • Prefer UITableViewHeaderFooterView for reusable or interactive section headers.
  • Reset reused header state carefully so one section does not inherit another section's UI.

Course illustration
Course illustration

All Rights Reserved.