I'm looking to build a view which will grow as subviews are added to it. At first the height will be zero, then should grow as I add subviews until it reaches a maximum size, and then I want it to become scrollable.
If I build this using a UIView inside a UIScrollView and set up the constraints from top to bottom manually, it works as expected. However, I feel like this should be possible using a UIStackView, but when I try either the scroll view doesn't grow, or the contentSize of the scrollView gets stuck at the maximum height, and won't scroll. I've been playing with this for a while and think it might actually be a bug with UIStackView and Autolayout, but hopefully I just missed something. Wrapping the UIStackView in a container UIView doesn't help.
Here's a complete view controller which shows the problem (tapping anywhere adds labels to the scroll view)
import UIKit
class DumbScrollStack: UIViewController {
let stack = UIStackView()
let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
//Setup
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(addLabel)))
stack.axis = .vertical
view.backgroundColor = .blue
scrollView.backgroundColor = .red
//Add scroll view and setup position
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
//Set maximum height
scrollView.heightAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true
//add stack
scrollView.addSubview(stack)
stack.translatesAutoresizingMaskIntoConstraints = false
//setup scrollView content size constraints
stack.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stack.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
stack.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
stack.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
stack.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
// Keep short if stack height is lower than scroll height
// With this constraint the scrollView grows as expected, but doesn't scroll when it goes over 100px
// Without this constraint the scrollView is always 100px tall but it does scroll
let constraint = scrollView.heightAnchor.constraint(equalTo: stack.heightAnchor)
constraint.priority = UILayoutPriority(rawValue: 999)
constraint.isActive = true
addLabel()
}
@objc func addLabel() {
let label = UILabel()
label.backgroundColor = .gray
label.text = "Hello"
stack.addArrangedSubview(label)
}
}
A streamlined interface for laying out a collection of views in either a column or a row.
Add this
label.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1000), for: .vertical)
or comment this block
let constraint = scrollView.heightAnchor.constraint(equalTo: stack.heightAnchor)
constraint.priority = UILayoutPriority(rawValue: 999)
constraint.isActive = true
or make this lower than 750 ( the label's default vertical compression )
constraint.priority = UILayoutPriority(rawValue: 749)
the problem is that the label's vertical compression priority is lower than 999 which always make the stack equal to the scrollview height , hence no scroll
Have you looked into using a UICollectionView? It sounds like what you're trying to rebuild.
Make sure to set "Scrolling Enabled" to true and set the scroll direction.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With