Reputation: 4570
My goal is to create a Stack View which can house an arbitrary (~5-10) arranged subviews and become scrollable should its height become taller than the view its contained in. For this I use a scroll view.
My understanding is that a Stack View whose distribution
is set to fill
should have an intrinsic content size if each arranged subview has an explicit height constraint. So I can add the Stack View to a scroll view and the scroll can get its content size from the intrinsic content size of the Stack View.
I'm also trying to make this Scrolling Stack View robust to changes in frame (from the keyboard).
I've spent a great deal of time and read many long articles on Scroll View and StackView and all of the programming guides but cannot get it to work perfectly.
The following code and slight variations on it always gives a content size is ambiguous
or Height is ambiguous for Scroll View
. When the keyboard pops up (from tapping on the textview in the Stack View) I just subtract 400 from the Scroll View's bottom constraint's constant. My thinking is that the Scroll View's frame/bounds will then become smaller than the Stack View's intrinsic content height and scrolling will occur. However, the screen just goes blank. No constraint logs in the console either.
I've spent a very large amount of time thinking through all the considerations in this scenario but it just seems beyond me. I'd be very grateful for any help or pointers on the subject of Stack View's in Scroll Views.
Here is my current experiment with it:
class ViewController: UIViewController {
let scrollView = UIScrollView()
var scrollViewBottomConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// stack view setup (one blue and hellow view at 100 height)
let stackView = UIStackView()
stackView.distribution = .fill
stackView.axis = .vertical
let v1 = UIView()
v1.backgroundColor = .blue
let v2 = UITextView()
v2.backgroundColor = .yellow
stackView.addArrangedSubview(v1)
stackView.addArrangedSubview(v2)
// scroll
scrollView.addSubview(stackView)
view.addSubview(scrollView)
// constraints for stack view arranged views
v1.heightAnchor.constraint(equalToConstant: 100).isActive = true
v2.heightAnchor.constraint(equalToConstant: 100).isActive = true
// pin scroll view in main view
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
// pin scroll view to stack view's bottom anchor
scrollViewBottomConstraint = scrollView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 0)
scrollViewBottomConstraint.isActive = true
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
stackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
// constrain "content view to main view and not scroll view."
stackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1).isActive = true
NotificationCenter.default.addObserver(self,
selector: #selector(ViewController.handleKeyboard),
name: Notification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(ViewController.handleKeyboard),
name: Notification.Name.UIKeyboardWillHide,
object: nil)
}
func handleKeyboard(notification: Notification) {
scrollViewBottomConstraint.constant = -400
}
}
Upvotes: 0
Views: 1270
Reputation: 12053
I'm adding here my working example:
class ViewController: UIViewController {
let scrollView = UIScrollView()
var scrollViewBottomConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// stack view setup (one blue and hellow view at 100 height)
let stackView = UIStackView()
stackView.distribution = .fill
stackView.axis = .vertical
let v1 = UIView()
v1.backgroundColor = .blue
let v2 = UITextView()
v2.backgroundColor = .yellow
let v3 = UITextView()
v3.backgroundColor = .green
let v4 = UITextView()
v4.backgroundColor = .brown
stackView.addArrangedSubview(v1)
stackView.addArrangedSubview(v2)
stackView.addArrangedSubview(v3)
stackView.addArrangedSubview(v4)
// scroll
scrollView.addSubview(stackView)
view.addSubview(scrollView)
// constraints for stack view arranged views
v1.heightAnchor.constraint(equalToConstant: 200).isActive = true
v2.heightAnchor.constraint(equalToConstant: 200).isActive = true
v3.heightAnchor.constraint(equalToConstant: 180).isActive = true
v4.heightAnchor.constraint(equalToConstant: 250).isActive = true
// pin scroll view in main view
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 100).isActive = true
// pin scroll view to stack view's bottom anchor
scrollViewBottomConstraint = scrollView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 0)
scrollViewBottomConstraint.isActive = true
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
stackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
// constrain "content view to main view and not scroll view."
stackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1).isActive = true
NotificationCenter.default.addObserver(self,
selector: #selector(ViewController.handleKeyboard),
name: Notification.Name.UIKeyboardWillShow,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(ViewController.handleKeyboard),
name: Notification.Name.UIKeyboardWillHide,
object: nil)
}
func handleKeyboard(notification: Notification) {
scrollViewBottomConstraint.constant = -400
}
}
Upvotes: 0