What is the difference between layoutSubviews and draw(_:), and what triggers each?
Predict first
How many layout passes do three setNeedsLayout calls produce, what does a second layoutIfNeeded do, and what does resizing do?
import UIKit
let w = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 100))
let root = UIView(frame: w.bounds)
w.addSubview(root)
final class L: UIView {
var n = 0
override func layoutSubviews() { n += 1; super.layoutSubviews() }
}
let lv = L(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
root.addSubview(lv)
lv.layoutIfNeeded()
let base = lv.n
lv.setNeedsLayout(); lv.setNeedsLayout(); lv.setNeedsLayout()
lv.layoutIfNeeded()
print(lv.n - base)
lv.layoutIfNeeded()
print(lv.n - base)
lv.frame.size = CGSize(width: 60, height: 60)
lv.layoutIfNeeded()
print(lv.n - base)
- 3, 3, 4
- 1, 1, 2
- 1, 2, 3
Predict first
Does setNeedsDisplay() mark a view that does not override draw(_:)?
import UIKit
final class D: UIView { override func draw(_ r: CGRect) {} }
let plain = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
plain.setNeedsDisplay()
let d = D(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
d.setNeedsDisplay()
print(plain.layer.needsDisplay(), d.layer.needsDisplay())
- true true
- false true
- false false
Mental model. setNeedsLayout is putting a sticky note “re-arrange the furniture” on the door; the room is re-arranged once, at the next tidy-up, however many notes you stuck on.
Layout and display are two independent passes. layoutSubviews() sets the geometry of a view’s subviews. It is triggered when the view is resized, when subviews are added or removed, when constraints change, when a scroll view’s offset changes, or when you call setNeedsLayout(). That call only marks the view; the layout runs at the next update cycle (end of the current run-loop turn), and many marks coalesce into one pass. layoutIfNeeded() forces a pending pass to run now, which is what you call inside an animation block to animate constraint changes. Calling layoutIfNeeded when nothing is pending does nothing. Never call layoutSubviews yourself, and do not change constraints inside it carelessly, since that can trigger another pass.
draw(_:) produces the view’s pixels with Core Graphics. It is triggered by setNeedsDisplay() (or setNeedsDisplay(_:) with a dirty rectangle) and when the view first appears. Crucially, a view whose class does not override draw(_:) has nothing to draw, so setNeedsDisplay() does nothing for it (the layer is not even marked) and the backing store is never created. Override it only when you need custom drawing; otherwise compose built-in views, use layers (CAShapeLayer), or render images. Both passes happen on the main thread inside the commit phase of the run loop, so slow work in either one costs frames. A good rule: layout for position and size, draw for appearance, and neither for data work.
Pending requests coalesce into one pass, a second layoutIfNeeded with nothing pending does nothing, and resizing the view marks it for layout again.
Without a draw(_:) override the view has no content of its own to redraw, so the request is ignored.
Common traps
- Calling
layoutSubviewsdirectly. UsesetNeedsLayoutandlayoutIfNeeded. - Overriding
draw(_:)with an empty body, or callingsetNeedsDisplayon a view with no draw. Either wastes a backing store or does nothing.
They might follow up with
- How do you animate a constraint change? Change it, then
layoutIfNeeded()inside the animation block. - Why does a scroll view call
layoutSubviewsconstantly? Its bounds origin changes on every scroll, which marks it for layout.
Related questions
- 9.1.1 How does Auto Layout work, and what are intrinsic size, content hugging, compression resistance, priorities…
- 9.2.1 Walk through how a frame gets from your code to the screen.
- 9.5.1 How do you draw with Core Graphics and UIBezierPath, and how do scale and caching affect it?
← How does Auto Layout work, and what are intrinsic size, content…