UITableView
UITableViewCell
iOS Development
Auto Layout
Smooth Scrolling

Jerky Scrolling After Updating UITableViewCell in place with UITableViewAutomaticDimension

Master System Design with Codemia

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

Introduction

Jerky scrolling in a self-sizing UITableView usually happens when cell content changes trigger expensive layout recalculation at the wrong time. With UITableView.automaticDimension, UIKit is constantly balancing estimated heights, actual Auto Layout results, and scroll position. If updates are too broad or too frequent, the table view reflows cells mid-scroll and the result feels unstable.

Understand Why Self-Sizing Cells Jitter

When a table view uses self-sizing cells, the height of each row is derived from Auto Layout constraints. That is convenient, but it also means every content change can invalidate layout and force UIKit to recalculate heights.

The standard setup looks like this:

swift
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 88

That alone is not the problem. The jitter usually appears when one of these happens:

  • calling reloadData() for a small in-place change
  • changing constraints repeatedly while the cell is on screen
  • giving UIKit poor estimated heights
  • doing async updates that arrive during active scrolling

The fix is to reduce layout churn and make height changes more predictable.

Update the Row Instead of Reloading the Whole Table

If only one visible cell changed, do not reload the entire table. A narrower update preserves more of the existing layout state.

swift
1func updateRow(at indexPath: IndexPath) {
2    tableView.performBatchUpdates({
3        tableView.reloadRows(at: [indexPath], with: .none)
4    })
5}

Using .none avoids extra animation that can make height changes feel even more erratic. performBatchUpdates or the older beginUpdates and endUpdates pair forces the table view to recompute layout in a controlled batch instead of across several unrelated passes.

For some cases, this older pattern is still perfectly fine:

swift
tableView.beginUpdates()
tableView.endUpdates()

That tells UIKit to recalculate self-sizing heights without throwing away the whole table state.

Keep Cell Constraints Stable

A self-sizing cell should not be changing its constraint structure every time new data arrives. The safest pattern is:

  • build a stable view hierarchy once
  • toggle text and visibility predictably
  • avoid adding and removing subviews on reuse

Example cell configuration:

swift
1final class MessageCell: UITableViewCell {
2    @IBOutlet private weak var bodyLabel: UILabel!
3    @IBOutlet private weak var detailsLabel: UILabel!
4
5    override func prepareForReuse() {
6        super.prepareForReuse()
7        bodyLabel.text = nil
8        detailsLabel.text = nil
9        detailsLabel.isHidden = false
10    }
11
12    func configure(body: String, details: String?) {
13        bodyLabel.text = body
14        detailsLabel.text = details
15        detailsLabel.isHidden = details == nil
16    }
17}

This is much smoother than dynamically adding or removing labels during reuse. Stable constraints give Auto Layout fewer surprises and reduce height thrashing while scrolling.

Use Realistic Estimated Heights

Bad estimates are a major source of visible jumps. If the estimated row height is wildly different from the actual height, the table view will keep correcting content offsets as real measurements arrive.

swift
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 120

Pick an estimate that is close to the average real row height in your screen. If you have multiple row families with very different sizes, consider using better estimate logic instead of one unrealistic global value.

For older delegate-based screens, you can also provide explicit estimates:

swift
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return 120
}

The estimate does not need to be exact, but it should not be absurdly small or large.

Defer Expensive Changes Until Scrolling Settles

If async content such as images or expanded text arrives while the user is actively scrolling, even correct row updates can feel rough. One pragmatic approach is to delay height-affecting updates until scrolling slows or ends.

swift
1func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
2    applyPendingRowUpdates()
3}
4
5func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
6    if !decelerate {
7        applyPendingRowUpdates()
8    }
9}

This is especially effective for feeds where remote data causes visible cells to grow after initial display.

Profile Before Blaming the Table View

Sometimes the table view is not the real bottleneck. If cells do heavy work in layoutSubviews, image decoding, or string measurement on the main thread, scrolling will still feel bad even if the height logic is technically correct.

Profile with Instruments and look for:

  • repeated layout passes
  • expensive constraint solving
  • image decoding on the main thread
  • unnecessary cell reconfiguration during scroll

A jerky self-sizing table is often half layout problem and half main-thread workload problem.

Common Pitfalls

  • Calling reloadData() for a one-row content change and forcing a full layout refresh.
  • Building cells with unstable constraints that change structure during reuse.
  • Using unrealistic estimated heights that cause constant scroll offset correction.
  • Applying height-affecting async updates while the user is actively dragging the table.
  • Blaming automaticDimension when the real issue is heavy main-thread work in cell configuration or layout.

Summary

  • Jerky scrolling usually comes from too many layout invalidations in a self-sizing table.
  • Update rows narrowly with performBatchUpdates or beginUpdates and endUpdates.
  • Keep cell constraints stable across reuse and configuration.
  • Use estimated heights that roughly match reality.
  • Profile layout and main-thread work together, because smooth scrolling depends on both.

Course illustration
Course illustration

All Rights Reserved.