In what order do updateConstraints, layoutSubviews and the layout calls run, and how do setNeedsLayout and layoutIfNeeded coalesce?
Predict first
What order and how many times do these hooks run?
import UIKit
final class Probe: UIView {
nonisolated(unsafe) static var log: [String] = []
override func updateConstraints() { Probe.log.append("update"); super.updateConstraints() }
override func layoutSubviews() { Probe.log.append("layout"); super.layoutSubviews() }
}
let win = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 600)); win.isHidden = false
let p = Probe(frame: CGRect(x: 0, y: 0, width: 100, height: 100)); win.addSubview(p)
let sub = UIView(); sub.translatesAutoresizingMaskIntoConstraints = false; p.addSubview(sub)
let w = sub.widthAnchor.constraint(equalToConstant: 10)
NSLayoutConstraint.activate([w, sub.heightAnchor.constraint(equalToConstant: 10)])
p.layoutIfNeeded(); Probe.log = []
p.setNeedsUpdateConstraints(); p.setNeedsLayout(); p.layoutIfNeeded(); print(Probe.log); Probe.log = []
p.setNeedsLayout(); p.setNeedsLayout(); p.setNeedsLayout(); p.layoutIfNeeded(); p.layoutIfNeeded(); print(Probe.log); Probe.log = []
w.constant = 20; p.layoutIfNeeded(); print(Probe.log)
- ["update", "layout"], then ["layout"], then ["layout"]
- ["layout", "update"], then ["layout", "layout", "layout"], then ["layout"]
- ["update", "layout"], then ["layout"], then ["update", "layout"]
Mental model. setNeedsLayout is putting a sticky note on the whiteboard; layoutIfNeeded is reading the notes now. Ten notes for the same job make one trip.
Auto Layout has two passes, both run by the system at the end of the run-loop turn. The constraint pass calls updateConstraints() bottom-up, only on views that asked via setNeedsUpdateConstraints(); it is where you add, remove or change constraints in one place. Then the layout pass calls layoutSubviews() top-down on views marked by setNeedsLayout() (or implicitly by a changed constraint, a size change or a new subview). So the order is updateConstraints then layoutSubviews. Marking is cheap and coalescing: calling setNeedsLayout() three times and layoutIfNeeded() twice still runs one pass, because the second call finds nothing pending (checked below).
Use layoutIfNeeded() to force the pending pass now, for example to read a frame or inside an animation block. updateConstraintsIfNeeded() runs only the constraint pass. In a view controller, override updateViewConstraints() in the same spirit. Changing a constant marks layout but not the constraint pass, which is why the third line of the check shows only layoutSubviews. Do not change constraints inside layoutSubviews without care: it invalidates layout during layout and can loop, and the engine will warn about it.
Constraints update first, layout once for any number of marks, and a constant change marks layout only.
Common traps
- Calling
layoutIfNeeded()expecting it to always lay out. It only runs if something is pending. - Changing constraints inside
layoutSubviews. It can trigger another pass and, at worst, a loop.
They might follow up with
- Where should constraint changes live? In
updateConstraints()when they depend on state; otherwise just setconstantorisActive. - Why can a frame be stale after changing a constraint? Layout has not run yet.
Related questions
- 9.1.2 What is the difference between layoutSubviews and draw(_:), and what triggers each?
- 9.8.4 Who owns a constraint, what can you change after activating it, and why is the multiplier read-only?
- 9.2.1 Walk through how a frame gets from your code to the screen.
← Who owns a constraint, what can you change after activating it, and… How do you measure a view or a wrapped label… →