Why is my UIView list not visible in my UIStackView and UIScrollView?

82 Views Asked by At

I have been messing with this code for a while. My UIViews are not showing up after looping through. I have tried this with out constraints, and the result was each of the UIViews were being rendered on top of each other. I would prefer to use the constraints, like I have it set up.

Can you take a look at this code and explain why my UIViews are not rendering and why my UIScrollViews are not scrolling?

import UIKit

class EventsListViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let scrollView = UIScrollView()
        let scrollViewConstraints: [NSLayoutConstraint] = [
            scrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
            scrollView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor)
        ]
        self.view.addSubview(scrollView)
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate(scrollViewConstraints)
        scrollView.backgroundColor = .gray
        
        let stackView = UIStackView(frame: scrollView.frame)
        let stackViewConstraints: [NSLayoutConstraint] = [
            stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
        ]
        scrollView.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate(stackViewConstraints)
        stackView.axis = .vertical
        stackView.distribution = .fill
        stackView.alignment = .fill
        stackView.contentMode = .top
        stackView.spacing = 5
        
        for event in location.myEvents! {
            let listItem = UIView()
            listItem.layer.name = event.eventID
            listItem.backgroundColor = .blue
            stackView.addArrangedSubview(listItem)
            let listItemConstraints: [NSLayoutConstraint] = [
                listItem.heightAnchor.constraint(equalToConstant: 35),
                listItem.widthAnchor.constraint(equalToConstant: stackView.frame.width)
            ]
            listItem.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate(listItemConstraints)
        }
        
    //       scrollView.contentSize = CGSize(width: Int(view.frame.width), height: (height + spacing * count))
        
    }
}

I have tried the following:

  • Without constraints results - all UIViews on top of each other
  • With constraints results - UIViews not rendering
2

There are 2 best solutions below

1
HangarRash On BEST ANSWER

The big issue I see is that you constrain the stack view to the exact size of the scroll view. UIScrollView has a frameLayoutGuide and a contentLayoutGuide. The stack view should be constrained to the contentLayoutGuide. This will allow the stack view to be sized properly based on the views you put in it and it sets the contentSize of the scroll view as needed. And it will also allow the stack view to scroll within the scroll view if large enough.

Here is a cleaned up version of your code with a bunch of comments. For testing purposes I simply hardcoded the creation of 20 views since I don't have your data model.

Note the change in distribution for the stack view. Using equalSpacing allows each view to have its own height. The height of the stack view will be made as tall as need to account for the total height of all view and the spacing. Using fill (as you had it) attempts to resize the height of the views to fill the height of the stack view. But you want the views to dictate the height of the stack view, not the other way around.

Note that I remove the width constraint on each view. The stack view will take care of the width due to the alignment of fill.

Also note the use of the frameLayoutGuide and contentLayoutGuide.

Lastly, the last constraint sets the width of the stack view. This is needed since the views in the stack view do not give the stack view an intrinsic width in this case.

override func viewDidLoad() {
    super.viewDidLoad()

    let scrollView = UIScrollView()
    scrollView.backgroundColor = .gray
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    self.view.addSubview(scrollView)

    let stackView = UIStackView()
    stackView.axis = .vertical
    stackView.distribution = .equalSpacing // let each view have its own height
    stackView.alignment = .fill // make each view fill the width of the stack view
    stackView.spacing = 5
    stackView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.addSubview(stackView)

    for _ in 0...20 {
        let listItem = UIView()
        //listItem.layer.name = event.eventID
        listItem.backgroundColor = .blue
        listItem.translatesAutoresizingMaskIntoConstraints = false
        stackView.addArrangedSubview(listItem)
        NSLayoutConstraint.activate([
            listItem.heightAnchor.constraint(equalToConstant: 35), // give each view a specific height
            // With an alignment of fill, the stack view will make the width of each view its own width
        ])
    }

    NSLayoutConstraint.activate([
        // Use frameLayoutGuide to constrain the visible portion of the scroll view
        scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
        scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
        scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
        scrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),

        // Use contentLayoutGuide to fit the scroll view's contentSize to the stack view
        stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
        stackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
        stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
        stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),

        // Give the stack view a fixed width to match the scroll view's frame, or give it some other width as desired
        stackView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),
    ])
}

enter image description here

2
son On

Autolayout with ScrollView is a bit tricky. As I see, you're missing a widthAnchor for stackView. Try debugging, you can easily see that your stackView has zero width, so, add this line:

...
stackView.widthAnchor.constraint(equalTo: self.view.widthAnchor)