Auto Layout
UITableView
Dynamic Cell Layouts
Variable Row Heights
iOS Development

Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

Master System Design with Codemia

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

Introduction

UITableView can calculate row heights automatically when the cell's content view has enough Auto Layout information to determine its vertical size. The two key ingredients are tableView.rowHeight = UITableView.automaticDimension and a cell layout that has an unbroken top-to-bottom constraint chain. If the constraints are incomplete or ambiguous, dynamic height stops being reliable fast.

Enable Automatic Dimension First

The table view must be told to use self-sizing rows.

swift
1override func viewDidLoad() {
2    super.viewDidLoad()
3
4    tableView.rowHeight = UITableView.automaticDimension
5    tableView.estimatedRowHeight = 100
6}

estimatedRowHeight does not need to be exact. Its job is to help the table estimate content size before the actual cells are measured.

Without automaticDimension, the rest of the Auto Layout work will not produce dynamic row heights.

Build the Cell with a Clear Vertical Constraint Chain

The cell's subviews must allow Auto Layout to infer the total height from the content.

For a simple title-plus-body cell, the vertical chain is usually:

  • top of content view to title label
  • title label to body label
  • body label to bottom of content view

The labels also need proper horizontal constraints.

A programmatic cell example:

swift
1import UIKit
2
3final class MessageCell: UITableViewCell {
4    let titleLabel = UILabel()
5    let bodyLabel = UILabel()
6
7    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
8        super.init(style: style, reuseIdentifier: reuseIdentifier)
9
10        titleLabel.font = .preferredFont(forTextStyle: .headline)
11        bodyLabel.font = .preferredFont(forTextStyle: .body)
12        bodyLabel.numberOfLines = 0
13
14        [titleLabel, bodyLabel].forEach {
15            $0.translatesAutoresizingMaskIntoConstraints = false
16            contentView.addSubview($0)
17        }
18
19        NSLayoutConstraint.activate([
20            titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
21            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
22            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
23
24            bodyLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
25            bodyLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
26            bodyLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
27            bodyLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12)
28        ])
29    }
30
31    required init?(coder: NSCoder) {
32        fatalError("init(coder:) has not been implemented")
33    }
34}

That bottom anchor on bodyLabel is essential. Without a constraint that reaches the bottom of the content view, the cell height is often ambiguous.

Configure the Table Normally

Once the cell layout is correct, ordinary table view configuration is enough.

swift
1class ViewController: UITableViewController {
2    let rows = [
3        ("Short", "One line"),
4        ("Longer", "This is a much longer body text that should wrap across multiple lines and force the cell to grow vertically.")
5    ]
6
7    override func viewDidLoad() {
8        super.viewDidLoad()
9        tableView.register(MessageCell.self, forCellReuseIdentifier: "MessageCell")
10        tableView.rowHeight = UITableView.automaticDimension
11        tableView.estimatedRowHeight = 100
12    }
13
14    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
15        rows.count
16    }
17
18    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
19        let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as! MessageCell
20        cell.titleLabel.text = rows[indexPath.row].0
21        cell.bodyLabel.text = rows[indexPath.row].1
22        return cell
23    }
24}

If the labels and constraints are correct, the long text row grows automatically.

Common Design Rules for Self-Sizing Cells

A few rules make self-sizing much more reliable:

  • set multiline labels to numberOfLines = 0
  • constrain subviews fully in both horizontal and vertical directions
  • anchor the last vertical element to the bottom of contentView
  • avoid manually setting frames on Auto Layout-managed subviews
  • prefer constraints inside contentView, not on the cell itself

These rules matter more than the exact visual design.

When Manual Height Calculation Is Usually Unnecessary

Older table view code often used heightForRowAt. With modern Auto Layout and self-sizing cells, that is often unnecessary. You should implement manual height calculation only when:

  • the layout is too complex for automatic sizing performance-wise
  • you already have a proven manual measurement path
  • you need a highly specialized layout optimization

For most content-driven cells, automatic dimension is simpler and easier to maintain.

Common Pitfalls

A common mistake is forgetting the bottom constraint from the last subview to the cell's contentView. Without it, the system cannot infer the final height correctly.

Another mistake is leaving multiline labels at their default single-line setting. Then long text truncates instead of expanding the cell.

People also often add constraints to the cell instead of contentView, which leads to confusing layout behavior.

Finally, mixing manual frame changes with Auto Layout constraints usually causes unpredictable results in self-sizing cells.

Summary

  • Dynamic row height in UITableView depends on both automaticDimension and a complete Auto Layout cell layout
  • Use an estimated row height and let the table compute the final height automatically
  • Build an unbroken top-to-bottom constraint chain inside the cell's contentView
  • Set multiline labels to numberOfLines = 0 when text should wrap
  • Prefer self-sizing cells over manual heightForRowAt unless you have a clear reason not to
  • Most failures come from incomplete constraints, not from the table view API itself

Course illustration
Course illustration

All Rights Reserved.