UITableView
iOS Development
Section Headers
iOS UI
Swift Programming

UITableView with fixed section headers

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A UITableView can keep section headers visible while scrolling, but the behavior depends on table style and on what kind of header you provide. In a plain-style table view, section headers naturally pin to the top until the next section pushes them away. Most confusion comes from using the wrong table style or customizing the header view in a way that fights the built-in behavior.

Use .plain Table Style for Pinned Headers

If you want the normal iOS sticky section-header behavior, create the table view with .plain.

swift
1import UIKit
2
3final class ViewController: UIViewController, UITableViewDataSource {
4    let tableView = UITableView(frame: .zero, style: .plain)
5    let data = [
6        (title: "A", rows: ["Apple", "Apricot"]),
7        (title: "B", rows: ["Banana", "Blueberry"])
8    ]
9
10    override func viewDidLoad() {
11        super.viewDidLoad()
12        tableView.dataSource = self
13        tableView.frame = view.bounds
14        view.addSubview(tableView)
15    }
16
17    func numberOfSections(in tableView: UITableView) -> Int { data.count }
18    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { data[section].rows.count }
19    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { data[section].title }
20    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
21        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
22        cell.textLabel?.text = data[indexPath.section].rows[indexPath.row]
23        return cell
24    }
25}

With .plain, the current section header sticks at the top automatically. You do not need to implement scroll-view hacks for the default behavior.

Custom Header Views Still Pin

If you need a custom appearance, implement viewForHeaderInSection instead of relying on the default title label.

swift
1func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
2    let label = UILabel()
3    label.text = "  \(data[section].title)"
4    label.backgroundColor = .secondarySystemBackground
5    label.font = .boldSystemFont(ofSize: 16)
6    return label
7}
8
9func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
10    36
11}

The header view will still pin in a plain table view. The important part is that you are customizing the section header, not adding a separate overlay view that only looks like one.

Know the Difference Between Section Headers and Table Header View

tableHeaderView is not the same thing as a section header. It sits above the entire table and scrolls with the content. If you want one header for each section that pins while that section is active, use section-header delegate methods.

That distinction explains a lot of bugs. Developers often add a tableHeaderView, expect sticky section behavior, and then spend time debugging behavior that is actually working exactly as designed.

Grouped Style Behaves Differently

A grouped or inset-grouped table view does not behave the same way visually as a plain table view. Depending on the iOS version and layout, grouped headers may not give the same pinned presentation you expect from plain tables.

If the product requirement is specifically "sticky section headers like Contacts", start with .plain and only move away from it if the visual design truly requires a grouped appearance.

Auto Layout and Sizing Still Matter

If a custom header view has ambiguous layout or an incorrect height, the table view can display awkward spacing or clipped content. Keep header views simple, set an explicit height when needed, and avoid heavy subview hierarchies unless the design genuinely needs them.

Pinned headers are a built-in behavior. Most rendering problems around them come from custom layout decisions rather than from the table view itself.

Common Pitfalls

  • Using tableHeaderView when the requirement is really a per-section sticky header.
  • Expecting grouped style to behave like plain style for pinned headers.
  • Adding scroll hacks for behavior that .plain already provides.
  • Returning a custom header view without matching height information.
  • Overcomplicating header layout and then blaming UITableView for clipping or spacing issues.

Summary

  • Use .plain style when you want the standard pinned section-header behavior.
  • Implement titleForHeaderInSection or viewForHeaderInSection for section headers.
  • 'tableHeaderView is a different feature and does not replace section headers.'
  • Keep custom header sizing and layout explicit.
  • Start with built-in table behavior before adding custom scroll logic.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.