添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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:

        private func setupViews() {
            scrollView.backgroundColor = .lightGray
            view.addSubview(scrollView)
            scrollView.addSubview(contentView)
            contentView.addSubview(stackView)
            for _ in 0...15 {
                stackView.addArrangedSubview(titleLabel)
    

    and here’s the final result!

    UIStackView Inside UIScrollView horizontal scroll issue fixed with more content.

    If you would like to see the full and finished code go to my Github repository: https://github.com/piotrchojnowski/StackScrollView

    This post was also posted on Medium: https://medium.com/@p.chojnowski/uistackview-inside-uiscrollview-uikit-28ef121a0fee

    Thanks for reading!