What rules does UIStackView follow: hidden views, distribution, who stretches, and what does removeArrangedSubview not do?

9.8.2 · L2 · Interview

Predict first

Three 50-point children in a 300-point stack with spacing 10: what happens when the middle one is hidden, then under fillEqually, then after removeArrangedSubview?

import UIKit

let win = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 600))
let host = UIView(frame: win.bounds); win.addSubview(host)
let s = UIStackView(); s.spacing = 10; s.translatesAutoresizingMaskIntoConstraints = false; host.addSubview(s)
NSLayoutConstraint.activate([s.leadingAnchor.constraint(equalTo: host.leadingAnchor), s.topAnchor.constraint(equalTo: host.topAnchor), s.widthAnchor.constraint(equalToConstant: 300)])
let a = UIView(), b = UIView(), d = UIView()
for x in [a, b, d] {
    x.heightAnchor.constraint(equalToConstant: 20).isActive = true
    x.widthAnchor.constraint(equalToConstant: 50).isActive = true
    s.addArrangedSubview(x)
}
host.layoutIfNeeded(); print(a.frame.width, d.frame.minX, d.frame.width)
b.isHidden = true; host.layoutIfNeeded(); print(d.frame.minX, d.frame.width)
s.distribution = .fillEqually; host.layoutIfNeeded(); print(a.frame.width, d.frame.width)
s.removeArrangedSubview(a); print(s.arrangedSubviews.count, s.subviews.count, a.superview === s)
  • 50.0 120.0 180.0, 60.0 240.0, 145.0 145.0, 2 3 true
  • 50.0 120.0 50.0, 60.0 50.0, 145.0 145.0, 2 2 false
  • 50.0 120.0 180.0, 120.0 180.0, 100.0 100.0, 2 3 true

Mental model. A stack view is a shelf with a rule: hidden books leave no gap, and the shelf squeezes or stretches the rest.

A UIStackView lays out its arranged subviews along an axis with spacing, alignment (across the axis) and distribution (along it). It generates constraints for you, so the children still need enough information to size themselves: their own width constraints or intrinsic size. Two behaviours are worth knowing exactly. Setting isHidden = true on an arranged subview collapses it: it takes no space and its spacing disappears, which is the idiomatic way to show or hide a row (and isHidden can be animated inside UIView.animate). And with the default .fill distribution, any extra space goes to the arranged subview with the lowest content-hugging priority; when priorities tie, the check below shows the last one stretching, so set hugging priorities explicitly instead of relying on that tie-break.

Other distributions change the rule: .fillEqually gives equal sizes (the hidden one excluded, so two visible views share the width minus spacing), .equalSpacing keeps the sizes and varies the gaps, and .fillProportionally uses intrinsic sizes as weights. A hidden view keeps its stale old frame, so do not read frames of hidden children. removeArrangedSubview(_:) only takes the view out of the layout: it stays in subviews and on screen, so call removeFromSuperview() as well when you really want it gone.

The last view stretches on a hugging tie; hiding the middle one removes it and one spacing (the last view now starts at 60 and is 240 wide); fillEqually splits the 280 left after one gap; and the removed arranged view is still a subview.

Common traps

  • Calling removeArrangedSubview and expecting the view to disappear. It is still a subview; remove it from its superview too.
  • Letting several children tie on hugging priority. Which one stretches is then an implementation detail.

They might follow up with

  • How do you hide a row with animation? Set isHidden inside an animation block.
  • When do you leave stack views for constraints? When you need relationships across rows or cells, or very large dynamic lists.

Related questions

  • 9.1.1 How does Auto Layout work, and what are intrinsic size, content hugging, compression resistance, priorities…
  • 9.8.3 What is a UILayoutGuide, and when is it better than an invisible spacer view?

← What are layout margins, how do they differ from the safe area, and…   What is a UILayoutGuide, and when is it better than an invisible… →