Explain the layout cycle. What is the difference between setNeedsLayout and layoutIfNeeded?
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
| Pass | Do the work (override) | Ask for it later | Force it now |
|---|---|---|---|
| Constraints | updateConstraints() | setNeedsUpdateConstraints() | updateConstraintsIfNeeded() |
| Layout | layoutSubviews() | setNeedsLayout() | layoutIfNeeded() |
| Display | draw(_:) | 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 correctWithout 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
superfirst. With Auto Layout,superapplies 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
layoutSubviewscause 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. UseviewDidLayoutSubviewsorviewIsAppearing. - What is the SwiftUI version? A custom
LayoutwithsizeThatFitsandplaceSubviews. SwiftUI also batches updates per frame.