iOS Development
UITableView
Custom Header
Swift Programming
Mobile App Design

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

Custom section headers in UITableView are useful when the default plain-text header is too limited for your design or interaction needs. They let you add typography, icons, buttons, and expand-collapse behavior while preserving table-view structure. The stable way to build them is with reusable UITableViewHeaderFooterView subclasses and a clear sizing strategy.

Start with the Simplest Option

If you only need plain text, the built-in header title API is enough.

swift
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return "Section \(section + 1)"
}

That is lightweight and perfectly valid for simple settings screens. Move to a custom header only when you need richer layout or interaction.

Return a Custom Header View

For custom visuals, implement viewForHeaderInSection.

swift
1import UIKit
2
3func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
4    let container = UIView()
5    container.backgroundColor = .secondarySystemBackground
6
7    let label = UILabel()
8    label.text = "Section \(section + 1)"
9    label.font = .preferredFont(forTextStyle: .headline)
10    label.translatesAutoresizingMaskIntoConstraints = false
11
12    container.addSubview(label)
13
14    NSLayoutConstraint.activate([
15        label.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
16        label.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16),
17        label.centerYAnchor.constraint(equalTo: container.centerYAnchor)
18    ])
19
20    return container
21}

This works, but for production code you usually want reuse and encapsulation.

Prefer UITableViewHeaderFooterView Reuse

Repeatedly creating ad hoc header views is less maintainable. A reusable header subclass is cleaner and scales better.

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 = .secondarySystemBackground
12
13        titleLabel.font = .preferredFont(forTextStyle: .headline)
14        titleLabel.translatesAutoresizingMaskIntoConstraints = false
15        contentView.addSubview(titleLabel)
16
17        NSLayoutConstraint.activate([
18            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
19            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
20            titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
21            titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)
22        ])
23    }
24
25    required init?(coder: NSCoder) {
26        fatalError("init(coder:) has not been implemented")
27    }
28
29    func configure(title: String) {
30        titleLabel.text = title
31    }
32}

Register it and dequeue it:

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

Sizing the Header Correctly

You need either fixed height or automatic sizing.

Fixed-height example:

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

Automatic sizing example:

swift
tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 44

If you use automatic sizing, your constraints must fully define the header’s vertical layout. Most clipped-header bugs come from incomplete constraints.

Add Buttons or Collapse Behavior

Headers often act as expand-collapse triggers. The important rule is to keep section state in the controller or view model, not inside the reusable header view.

swift
1final class TappableHeaderView: UITableViewHeaderFooterView {
2    var onTap: (() -> Void)?
3
4    override init(reuseIdentifier: String?) {
5        super.init(reuseIdentifier: reuseIdentifier)
6
7        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
8        contentView.addGestureRecognizer(tap)
9    }
10
11    required init?(coder: NSCoder) {
12        fatalError("init(coder:) has not been implemented")
13    }
14
15    @objc private func handleTap() {
16        onTap?()
17    }
18}

Then in the table-view controller, update the model and reload the affected section.

Accessibility and Appearance

Custom headers should behave like real headers, not just decorative views.

Useful settings:

swift
header.accessibilityTraits = .header
header.isAccessibilityElement = true

Also check:

  • Dynamic Type support.
  • Dark-mode contrast.
  • Enough tap target size for interactive headers.

If the header contains controls, make sure those controls are discoverable and not hidden behind a gesture-only interaction.

Common Pitfalls

  • Building header views from scratch every time instead of using reusable header-footer views. Fix by subclassing UITableViewHeaderFooterView.
  • Storing section-expanded state inside the header view. Fix by keeping state in the controller or view model.
  • Mixing manual frames with Auto Layout in the same header. Fix by choosing one layout system and completing all constraints.
  • Using automatic height without vertical constraints. Fix by constraining top and bottom edges of header content.
  • Forgetting accessibility semantics on custom headers. Fix by setting header traits and validating with VoiceOver.

Summary

  • Use simple built-in titles when plain text is enough.
  • For richer UI, prefer reusable UITableViewHeaderFooterView subclasses.
  • Keep sizing strategy explicit with either fixed heights or complete Auto Layout constraints.
  • Put interactive state in the controller, not in the reusable header view.
  • Treat custom headers as real UI components with proper accessibility and reuse behavior.

Course illustration
Course illustration

All Rights Reserved.