Who owns a constraint, what can you change after activating it, and why is the multiplier read-only?
Predict first
Where do the constraints live before and after activation?
import UIKit
let host = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 600))
let a = UIView(), b = UIView()
for x in [a, b] { x.translatesAutoresizingMaskIntoConstraints = false; host.addSubview(x) }
let width = a.widthAnchor.constraint(equalToConstant: 10)
let gap = b.leadingAnchor.constraint(equalTo: a.trailingAnchor, constant: 8)
print(width.isActive, host.constraints.count)
NSLayoutConstraint.activate([width, gap])
print(width.isActive, a.constraints.count, host.constraints.count, host.constraints.contains(gap), a.constraints.contains(width))
gap.isActive = false
print(host.constraints.count)
- false 0, then true 1 1 true true, then 0
- false 0, then true 0 2 true false, then 1
- true 1, then true 1 1 false true, then 1
Predict first
Does assigning a new multiplier to an existing constraint compile?
import UIKit
let v = UIView()
let c = v.widthAnchor.constraint(equalTo: v.heightAnchor, multiplier: 2)
c.multiplier = 3
- Yes, then the layout updates
- No: multiplier is get-only
- Yes, but it crashes at run time
Mental model. A constraint is a fixed rule written on a notice board of the common ancestor; you can tweak its number, but to change the rule itself you pin a new notice.
An active constraint is stored on a view: the nearest common ancestor of the items it connects (a width constraint on a single view lives on that view itself; a spacing between two siblings lives on their parent). view.constraints lists only the constraints it owns, which is why it can look empty for a view that is clearly constrained. isActive = true installs a constraint on the right owner and NSLayoutConstraint.activate(_:) does it for a batch, which is faster than activating one by one; isActive = false removes it. A constraint that was created but not activated does nothing and is owned by nobody.
The fields of a constraint are fixed at creation: its items, attributes, relation and multiplier are read-only (the check below fails to compile when assigning to multiplier), so to change a ratio you deactivate the old constraint and activate a new one. What you may change is the constant (the cheap, common case, and it is animatable by calling layoutIfNeeded() inside an animation block), the isActive flag, and the priority. Old documentation says you may not change a priority between required and optional after activation; I could not provoke an exception for that in the iOS 27 simulator, but do not depend on it: keep two constraints (one required, one lower) and toggle isActive instead. Give constraints identifiers so the console log names them.
The single-view width sits on the view, the sibling gap on the parent, and deactivating removes it.
The compiler rejects the assignment; create and activate a replacement constraint.
Common traps
- Counting
view.constraintsto decide whether a view is constrained. Constraints live on the common ancestor, not on each item. - Recreating constraints to change a number. Update
constantand let the engine re-solve.
They might follow up with
- How do you animate a constraint change? Change
constant, then calllayoutIfNeeded()inside the animation block. - How do you switch between two layouts? Keep two groups of constraints and flip
isActive.
Related questions
- 9.1.1 How does Auto Layout work, and what are intrinsic size, content hugging, compression resistance, priorities…
- 9.8.5 In what order do updateConstraints, layoutSubviews and the layout calls run, and how do setNeedsLayout and…
- 9.1.2 What is the difference between layoutSubviews and draw(_:), and what triggers each?
← What is a UILayoutGuide, and when is it better than an invisible… In what order do updateConstraints, layoutSubviews and the layout… →