I guess you might be wondering how to auto-layout properly
UIStackView
inside
UIScrollView
, since you are reading this post.
The thing about auto-layout trick is to setup auto-layout properly, so the
UIScrollView
will not show horizontal or vertical scrolls when not needed and it’s content size will be set properly.
In this short tutorial I will show you how to build simple UI with use of:
UIStackView
UIScrollView
Programmatic Auto-Layout
Ok, first things first. We are going to build a version of the app where we want to have a proper horizontal scrolling of contents of the stack view. I assume you created a
UIViewController
and the view is ready to work on.
If not, you can try out by making a new starter project in Xcode - Single View app.
Ok, first step, let’s add a
UIScrollView
and
UIStackView
object declarations by this:
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = UIStackView.Distribution.equalSpacing
stackView.spacing = 30
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
Ok, now let’s build two methods that will create subviews, add them to the hierarchy and setup layout.
We want also the StackView to have a 20 points layout margins on the leading and trailing side, by adding:
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -20).isActive = true
ok, to sum up, this part we should have something like this below.
private func setupViews() {
scrollView.backgroundColor = .lightGray
view.addSubview(scrollView)
scrollView.addSubview(stackView)
private func setupLayout() {
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -20).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
We are adding here
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
to tell the UIScrollView
how to calculate it’s content size for vertical scrolling only. See reference provided by Apple:
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html
Now let’s add some labels by first, declaration of computed property and then the actual usage.
var titleLabel: UILabel {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "UIStackView inside UIScrollView."
label.font = UIFont.systemFont(ofSize: 24, weight: .medium)
label.textColor = .white
label.textAlignment = .center
return label
/// Update setupViews() method at the end by adding this:
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(titleLabel)
Ok, now let’s compile it and run!
Below is what you should get.
UIStackView Inside UIScrollView swift iOS.
To avoid this issue, we have to provide a new content view and then add our UIStackView
into it.
Fixing the issue
First, create a new content view that will be holding the UIStackView
lazy var contentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
then, change the setupViews
method to reflect adding the contentView
into the scrollView
and add stackView
to contentView
as a subview:
private func setupViews() {
scrollView.backgroundColor = .lightGray
view.addSubview(scrollView)
scrollView.addSubview(contentView)
contentView.addSubview(stackView)
stackView.addArrangedSubview(simpleView)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(titleLabel)
Now let’s update the setupLayout
method to reflect the change in layouting.
private func setupLayout() {
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// because: "Constraints between the height, width, or centers attach to the scroll view’s frame." -
// https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20).isActive = true
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
Now we should get this fixed:
UIStackView Inside UIScrollView horizontal scroll issue fixed.
as you can see the margins now are correct and there is no possible scrolling, because the content does not need that! :)
Let’s try to add more labels by copying few more lines (just to fill empty space) in setupViews
method: