Reputation: 16361
I have an array of labels: [UILabel]
, the count is according to the number of elements in an array. I'm adding them into a UIStackView
by looping over them. At a point, the number of UILabel
s exceeds the frame of the UIStackView
in this situation I want the UILabel
s that are added first to be shown and those added later to be hidden. What happens is the exact opposite. Like this:-
I would like text 1
preferred to be shown over text 2
and so on. Here's the code:
override func viewDidLoad() {
super.viewDidLoad()
let stackView = UIStackView()
stackView.alignment = .top
stackView.axis = .vertical
view.addSubview(stackView)
stackView.center = view.center
stackView.frame.size = CGSize(width: 100, height: 24)
(0..<4).forEach {
let label = UILabel()
label.text = "text \($0)"
label.backgroundColor = .red
stackView.addArrangedSubview(label)
}
}
FYI: I do not want to reverse the order.
Update: Thanks to @DonMag and @Rob I've set content compression resistance priority in my code. Unfortunately, I'm getting a different result for some reason from what @DonMag has depicted in his screenshots. Here's my updated code:
override func viewDidLoad() {
super.viewDidLoad()
let stackView = UIStackView()
stackView.alignment = .leading
stackView.axis = .vertical
view.addSubview(stackView)
stackView.frame = CGRect(x: 100, y: 100, width: 100, height: 150)
(0..<15).forEach {
let label = UILabel()
label.text = "text \($0)"
label.backgroundColor = .red
stackView.addArrangedSubview(label)
}
var p: Float = 1000
for v in stackView.arrangedSubviews {
v.setContentCompressionResistancePriority(UILayoutPriority(rawValue: p), for: .vertical)
p -= 1
}
}
And here's the result that I'm talking about:
Upvotes: 0
Views: 2334
Reputation: 77690
This should do it:
var p: Float = 1000
for v in stackView.arrangedSubviews {
v.setContentCompressionResistancePriority(UILayoutPriority(rawValue: p), for: .vertical)
p -= 1
}
A bit of an odd way to use a stack view though, and it may not give you what you want after all.
For example, with a 100 x 150
frame, and 14 labels, we get:
as we see, the bottom label is a little "off."
And, with only 5 labels, we get:
The top label height gets stretched, unless we do the same thing with Content Hugging priority... in which case the bottom label would get stretched.
A better route would be to embed the stack view in a UIView
, constrain Top / Leading / Trailing, set the height of the "container" UIView
, and then make sure to set .clipsToBounds = true
on the container view.
With 14 labels:
With 5 labels:
Here's the code to play with...
Method 1:
class FrankMethod1ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stackView = UIStackView()
stackView.alignment = .leading
stackView.axis = .vertical
view.addSubview(stackView)
stackView.frame = CGRect(x: 100, y: 100, width: 100, height: 150)
// change to (0..<5) to see the stretching
(0..<15).forEach {
let label = UILabel()
label.text = "text \($0)"
label.backgroundColor = .red
stackView.addArrangedSubview(label)
}
var p: Float = 1000
for v in stackView.arrangedSubviews {
v.setContentCompressionResistancePriority(UILayoutPriority(rawValue: p), for: .vertical)
p -= 1
}
}
}
Method 2:
class FrankMethod2ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let cView = UIView()
cView.frame = CGRect(x: 100, y: 100, width: 100, height: 150)
cView.clipsToBounds = true
// so we can see the view frame
cView.backgroundColor = .cyan
view.addSubview(cView)
let stackView = UIStackView()
stackView.alignment = .leading
stackView.axis = .vertical
cView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: cView.topAnchor),
stackView.leadingAnchor.constraint(equalTo: cView.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: cView.trailingAnchor),
])
// change to (0..<5) to see NO stretching
(0..<15).forEach {
let label = UILabel()
label.text = "text \($0)"
label.backgroundColor = .red
stackView.addArrangedSubview(label)
}
}
}
Edit
I don't think it's uncommon to see differences between Playground execution and Sim / Device.
Here's what I get:
when running this code in a Playground ... top two are "Method 1" with 15 & 5 labels, bottom two are "Method 2" with 15 & 5 labels:
import UIKit
import PlaygroundSupport
class PlaygroundFrankViewController: UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let stackView1 = UIStackView()
stackView1.alignment = .leading
stackView1.axis = .vertical
view.addSubview(stackView1)
stackView1.frame = CGRect(x: 50, y: 50, width: 100, height: 150)
// change to (0..<5) to see the stretching
(0..<15).forEach {
let label = UILabel()
label.text = "text \($0)"
label.backgroundColor = .red
stackView1.addArrangedSubview(label)
}
var p: Float = 1000
for v in stackView1.arrangedSubviews {
v.setContentCompressionResistancePriority(UILayoutPriority(rawValue: p), for: .vertical)
p -= 1
}
let stackView2 = UIStackView()
stackView2.alignment = .leading
stackView2.axis = .vertical
view.addSubview(stackView2)
stackView2.frame = CGRect(x: 200, y: 50, width: 100, height: 150)
// change to (0..<5) to see the stretching
(0..<5).forEach {
let label = UILabel()
label.text = "text \($0)"
label.backgroundColor = .red
stackView2.addArrangedSubview(label)
}
p = 1000
for v in stackView2.arrangedSubviews {
v.setContentCompressionResistancePriority(UILayoutPriority(rawValue: p), for: .vertical)
p -= 1
}
let cView1 = UIView()
cView1.frame = CGRect(x: 50, y: 220, width: 100, height: 150)
cView1.clipsToBounds = true
// so we can see the view frame
cView1.backgroundColor = .cyan
view.addSubview(cView1)
let stackView3 = UIStackView()
stackView3.alignment = .leading
stackView3.axis = .vertical
cView1.addSubview(stackView3)
stackView3.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView3.topAnchor.constraint(equalTo: cView1.topAnchor),
stackView3.leadingAnchor.constraint(equalTo: cView1.leadingAnchor),
stackView3.trailingAnchor.constraint(equalTo: cView1.trailingAnchor),
])
// change to (0..<5) to see NO stretching
(0..<15).forEach {
let label = UILabel()
label.text = "text \($0)"
label.backgroundColor = .red
stackView3.addArrangedSubview(label)
}
let cView2 = UIView()
cView2.frame = CGRect(x: 200, y: 220, width: 100, height: 150)
cView2.clipsToBounds = true
// so we can see the view frame
cView2.backgroundColor = .cyan
view.addSubview(cView2)
let stackView4 = UIStackView()
stackView4.alignment = .leading
stackView4.axis = .vertical
cView2.addSubview(stackView4)
stackView4.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView4.topAnchor.constraint(equalTo: cView2.topAnchor),
stackView4.leadingAnchor.constraint(equalTo: cView2.leadingAnchor),
stackView4.trailingAnchor.constraint(equalTo: cView2.trailingAnchor),
])
// change to (0..<5) to see NO stretching
(0..<5).forEach {
let label = UILabel()
label.text = "text \($0)"
label.backgroundColor = .red
stackView4.addArrangedSubview(label)
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = PlaygroundFrankViewController()
Upvotes: 1