How do autoresizing masks work, how does the extra space get shared, and how do they relate to Auto Layout?
Predict first
A 100×50 child at (20,20) in a 200×200 parent that grows to 300×400. Where does it end up for each mask?
import UIKit
func after(_ mask: UIView.AutoresizingMask) -> CGRect {
let sup = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
let kid = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 50))
kid.autoresizingMask = mask; sup.addSubview(kid)
sup.frame.size = CGSize(width: 300, height: 400)
return kid.frame
}
print(after([]))
print(after(.flexibleWidth))
print(after([.flexibleLeftMargin, .flexibleTopMargin]))
print(after([.flexibleWidth, .flexibleHeight]))
print(String(format: "%.2f", after([.flexibleWidth, .flexibleRightMargin]).width), UIView().translatesAutoresizingMaskIntoConstraints)
- unchanged; width 200; moved to (120, 220); 200×250; 155.33 true
- unchanged; width 300; moved to (20, 20); 300×400; 155.56 false
- unchanged; width 200; moved to (120, 220); 200×250; 200.00 true
Mental model. Each flexible part is a rubber band; the more bands, the more the stretch is shared out by how long each band already is.
Before Auto Layout, a view positioned itself relative to its superview with an autoresizing mask: when the superview’s size changes, each of the child’s six flexible parts (flexibleLeftMargin, flexibleWidth, flexibleRightMargin, and the top, height and bottom equivalents) absorbs a share of the change. With one flexible part it takes all of it; with several, the change is shared in proportion to their current sizes. For a 100-wide child at x = 20 in a 200-wide parent growing by 100, flexibleWidth alone gives width 200; [.flexibleLeftMargin, .flexibleTopMargin] moves the child by (100, 200); and [.flexibleWidth, .flexibleRightMargin] splits the growth between the width and the margin, so the width ends at about 155.33 (measured; the exact share is UIKit’s own arithmetic, so do not hand-compute it, give one part the flexibility instead). The default mask is empty, so a plain view just keeps its frame.
The two systems meet in one flag: translatesAutoresizingMaskIntoConstraints is true for code-created views, meaning the mask and frame are converted into constraints. Add your own constraints without turning it off and they conflict with the generated ones, the classic “unsatisfiable constraints” log. Set it to false on any view you constrain in code (Interface Builder does that for you). Autoresizing is still the simplest right choice for a child that should just fill its parent ([.flexibleWidth, .flexibleHeight], as in a hosted subview), and it is how frame-based legacy code survives next to constraints.
Each flexible part takes its share of the 100 and 200 points of growth, shared proportionally when more than one is flexible.
Common traps
- Forgetting
translatesAutoresizingMaskIntoConstraints = false. The generated constraints fight yours. - Mixing frames and constraints on one view. Pick one system per view.
They might follow up with
- Which views are false by default? Views from Interface Builder; code-created views are true.
- When is autoresizing the right tool? A child that simply fills its parent.
Related questions
- 9.1.1 How does Auto Layout work, and what are intrinsic size, content hugging, compression resistance, priorities…
- 9.8.4 Who owns a constraint, what can you change after activating it, and why is the multiplier read-only?
- 7.2.2 What is the difference between frame and bounds, and what happens to them under a transform?