iosNudge

Explain the layout cycle. What is the difference between setNeedsLayout and layoutIfNeeded?

MediumAsked oftenUIKitPerformance 2 min read

TL;DR

UIKit does three passes, top to bottom, before it renders a frame: update constraints, layout, display. Each pass has a "mark as dirty" method and a method that does the work. setNeedsLayout() marks the view dirty. Layout happens later, once, at the end of the run loop. layoutIfNeeded() runs layout now, if something is dirty.

The three passes

PassDo the work (override)Ask for it laterForce it now
ConstraintsupdateConstraints()setNeedsUpdateConstraints()updateConstraintsIfNeeded()
LayoutlayoutSubviews()setNeedsLayout()layoutIfNeeded()
Displaydraw(_:)setNeedsDisplay()(none, happens at render)

Never call layoutSubviews(), updateConstraints() or draw(_:) directly. Ask UIKit to do it.

Why "later" is good

Many changes in one run loop pass become one layout pass.

titleLabel.text = "New title"
subtitleLabel.text = "New subtitle"
badge.isHidden = false
// Each change calls setNeedsLayout() inside UIKit.
// Layout runs once at the end, not three times.

This batching is why UIKit is fast.

When you need layoutIfNeeded()

1. Animating constraints

topConstraint.constant = 0
UIView.animate(withDuration: 0.25) {
    self.view.layoutIfNeeded() // layout inside the animation block = animated frames
}

Call it on the superview that contains the changing views. Some people also call layoutIfNeeded() before the animation. This flushes old pending changes so they do not animate too.

2. Reading a correct frame right away

stackView.addArrangedSubview(newView)
stackView.layoutIfNeeded()
print(newView.frame) // now correct

Without layoutIfNeeded(), the frame would still be .zero.

Writing layoutSubviews

final class AvatarView: UIView {
    private let imageView = UIImageView()

    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = bounds
        layer.cornerRadius = bounds.width / 2
    }
}

Rules:

  • Call super first. With Auto Layout, super applies the constraint results.
  • Only set frames of subviews. Do not change your own frame.
  • Do not change constraints here. It can cause an infinite layout loop.
  • Keep it fast. It can run many times.

setNeedsDisplay is different

It is only for custom drawing in draw(_:). Changing a frame does not redraw content by default. Changing a label's text calls it for you.

var progress: CGFloat = 0 {
    didSet { setNeedsDisplay() }
}

updateConstraints

Apple recommends changing constraints right where the change happens. Use updateConstraints() only when many changes happen together and batching gives a real speed win. If you override it, call super last.

Common follow-up questions

  • What triggers layout automatically? Changing the view size, adding or removing subviews, rotation, and constraint changes.
  • Can layoutSubviews cause an infinite loop? Yes, if it changes something that marks the same view dirty again.
  • Why is my frame zero in viewDidLoad? Layout has not run yet. Use viewDidLayoutSubviews or viewIsAppearing.
  • What is the SwiftUI version? A custom Layout with sizeThatFits and placeSubviews. SwiftUI also batches updates per frame.