iosswiftuiscrollviewuistackview

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


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:


Solution

  • 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