Say you have, for example, a vertical stack view. Its alignment is leading (because you want most items to shrink left).
However you have one special type of item you want to be full width.
I have always done some sort of complicated solution to achieve this:
class FullWidthThing: .. {
..
translatesAutoresizingMaskIntoConstraints = false
private lazy var fullWidth: NSLayoutConstraint = {
guard let sv = superview else { return NSLayoutConstraint() }
let v = widthAnchor.constraint(equalTo: sv.widthAnchor)
v.isActive = true
return v
}()
override func layoutSubviews() {
if superview != nil {
_ = fullWidth
}
super.layoutSubviews()
}
}
But is it actually just as simple as setting the intrinsic with to .greatestFiniteMagnitude
? Superficial testing shows this seems to work:
private lazy var setup: () = {
translatesAutoresizingMaskIntoConstraints = false
setContentCompressionResistancePriority(.required, for: .horizontal)
...
return ()
}()
override var intrinsicContentSize: CGSize {
var sz = super.intrinsicContentSize
sz.width = .greatestFiniteMagnitude
return sz
}
Should this work, or is there a problem with doing this, or is there a common way of doing this?
For any view, Auto-layout -- whether in a stack view or other hierarchy -- will use the .intrinsicContentSize unless something else affects the size.
Consider this layout:
The Yellow frame is a Vertical Stack View, with Top and Leading constraints, spacing: 12 and alignment: Leading
Each outline frame is also a vertical stack view, distribution: fill equally, spacing: 4 and alignment: Leading, containing a label and a (default) UIView
.
We cannot see the views, because they have no width.
Each "sub" stack view has a width matching the intrinsic width of its label, and the outer stack view has a width of the widest "sub" stack view.
Outer Stack width: 300.0 (intrinsic width of widest label)
Now, let's add another sub-stack view with a label that is too wide to fit the screen:
Outer Stack width: 833.6666666666666
Next, let's add a Trailing constraint to the outer stack view:
Outer Stack width: 353.0
and, with the longer 4th string:
Outer Stack width: 353.0 (still)
So, what happens if, instead of a default UIView
we use a subclassed "wide" view:
class WideView: UIView {
override var intrinsicContentSize: CGSize {
var sz = super.intrinsicContentSize
sz.width = .greatestFiniteMagnitude
return sz
}
}
The value of .greatesFiniteMagnitude
is 1.7976931348623157e+308
... if we format that as a float ("%0.0f") we get a number that is 309 digits long.
Here's what we get with the first 3 strings, NO Trailing anchor on the outer stack view:
Outer Stack width: 2777777.0 (greatest width allowed by UIKit)
If we add the longer string:
It looks the same, and again:
Outer Stack width: 2777777.0
Add the outer stack view Trailing anchor:
Outer Stack width: 353.0
Here's the class I used to generate those outputs (see the comments and commented lines):
class WideTestVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
let strs: [String] = [
"Short",
"Medium length",
"Longer string for width testing",
//"This string will be too long to fit the available width of a phone in portrait orientation!",
]
let colors: [UIColor] = [
.systemRed,
.systemGreen,
.systemBlue,
.cyan,
]
let outerStack = UIStackView()
outerStack.axis = .vertical
outerStack.alignment = .leading
outerStack.spacing = 12
for (str, c) in zip(strs, colors) {
let st = UIStackView()
st.axis = .vertical
st.alignment = .leading
st.distribution = .fillEqually
st.spacing = 4
st.layer.borderColor = UIColor.darkGray.cgColor
st.layer.borderWidth = 1
let label = UILabel()
label.font = .systemFont(ofSize: 24.0, weight: .light)
label.text = str
label.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
// to see the difference...
// use standard UIView (will have no width)
let v = UIView()
// or, use custom "WideView" with width: .greatestFiniteMagnitude
//let v = WideView()
v.backgroundColor = c
st.addArrangedSubview(label)
st.addArrangedSubview(v)
outerStack.addArrangedSubview(st)
}
outerStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(outerStack)
let g = view.safeAreaLayoutGuide
// constrain ONLY Top / Leading, or
// constrain Top / Leading / Trailing
NSLayoutConstraint.activate([
outerStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 30.0),
outerStack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
//outerStack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
outerStack.backgroundColor = .yellow
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let v = view.subviews.first as? UIStackView
else { return }
print(v.frame.width)
}
}