1

I have spent a long time trying to stack the views that I create programmatically. I looked at examples from Add views in UIStackView programmatically but that didn't work. Listed below is the code, I am calling the setUpListings from the view controller. There are two entries but only one entry is shown.

import UIKit
import SnapKit

class ListingsView : UIView {
    var containerView: UIView!
    var listingsContainerView: UIStackView!

    init() {
        super.init(frame: CGRect.zero)
        setUpContainerView()
        setUpListingsContainer()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    func setUpContainerView() {
        containerView = UIView()

        self.addSubview(containerView)

        containerView.snp.makeConstraints { (make) in
            make.height.equalTo(self)
            make.width.equalTo(self)

            containerView.backgroundColor = UIColor.white
        }
    }

    func setUpListingsContainer() {
        listingsContainerView = UIStackView()
        listingsContainerView.distribution = .equalSpacing
        listingsContainerView.alignment = .fill
        listingsContainerView.axis = .vertical
        listingsContainerView.spacing = 10
        listingsContainerView.translatesAutoresizingMaskIntoConstraints = false

        containerView.addSubview(listingsContainerView)

        listingsContainerView.snp.makeConstraints { (make) in
            make.top.equalTo(containerView)
            make.left.equalTo(containerView)
            make.bottom.equalTo(containerView)
            make.right.equalTo(containerView)
        }
    }

    func setUpListings(listings: [Listing]) {
        for listing in listings {
            let listingEntry = ListingEntry(listingId: listing.id)
            listingsContainerView.addArrangedSubview(listingEntry)
        }
    }

    class ListingEntry : UIView {
        var listingId: String?
        var containerView: UIView!

        init(listingId: String) {
            super.init(frame: CGRect.zero)
            self.listingId = listingId
            self.setUpContainerView()
        }

        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }

        func setUpContainerView() {
            containerView = UIView()
            containerView.backgroundColor = UIColor.gray
            self.addSubview(containerView)

            containerView.snp.makeConstraints { (make) in
                make.width.equalTo(150)
                make.height.equalTo(150)
            }
        }
    }
}

The view currently looks like enter image description here

But the blocks should be stacked.

AlwaysNull
  • 348
  • 2
  • 4
  • 15
  • it looks like that you have problems with constraints. Why did you even add `listingsContainerView` configuration into `makeConstraints` closure? In `containerView.snp.makeConstraints` closure you don't even use `make` input, but use anchors. That is wrong. – Lew Winczynski Jan 26 '20 at 22:40
  • AlwaysNull --- please review [ask]. You have not described or shown what you're trying to do... *"I looked at examples ... but that didn't work"* doesn't tell us anything. Describe what you want to get... maybe include an image of your goal... show the code you tried to follow from examples (***NOT*** what you tried here with SnapKit, as it's all very, very wrong). – DonMag Jan 28 '20 at 14:06
  • @DonMag I updated the post, please point out where I am doing these wrong with SnapKit. – AlwaysNull Jan 28 '20 at 23:10

1 Answers1

5

Couple things...

First, I'd suggest learning how constraints and auto-layout work before using something like SnapKit. It can make some things easier --- but until one has a good understanding of the fundamentals, it's not clear what's doing what.

Second, during development, it helps to give views and subviews contrasting background colors. Makes it much easier to see what's happening to the frames at run-time.

So, if you're going to stick with SnapKit...

Try to keep code "clean." That is, don't put anything inside a snp.makeConstraints block that isn't directly related (such as setting background colors).

In your ListingEntry class, you're adding a subview (containerView) and giving that view a width and height of 150, but you are not constraining it to its superview... which results in a view height of Zero.

Take a look at the modifications I made to your code. I added comments that should make the changes clear:

class MiscViewController: UIViewController {

    var listingsView: ListingsView = {
        let v = ListingsView()
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(listingsView)

        listingsView.backgroundColor = .red

        // constrain listingsView to all 4 sides with 40-pt "padding"
        listingsView.snp.makeConstraints { (make) in
            make.top.bottom.leading.trailing.equalToSuperview().inset(40.0)
        }

        let listings: [Listing] = [
            Listing(id: "A"),
            Listing(id: "B"),
            Listing(id: "C"),
        ]

        listingsView.setUpListings(listings: listings)

    }

}

struct Listing {
    var id: String = ""
}

class ListingsView : UIView {
    var containerView: UIView!
    var listingsContainerView: UIStackView!

    init() {
        super.init(frame: CGRect.zero)
        // probably want to set clipsToBounds so any content doesn't extend outside the frame
        clipsToBounds = true
        setUpContainerView()
        setUpListingsContainer()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    func setUpContainerView() {
        containerView = UIView()

        self.addSubview(containerView)

        containerView.backgroundColor = UIColor.green

        // constrain containerView to all 4 sides
        containerView.snp.makeConstraints { (make) in
            make.top.bottom.leading.trailing.equalToSuperview()
        }
    }

    func setUpListingsContainer() {
        listingsContainerView = UIStackView()
        listingsContainerView.distribution = .equalSpacing
        listingsContainerView.alignment = .fill
        listingsContainerView.axis = .vertical
        listingsContainerView.spacing = 10

        containerView.addSubview(listingsContainerView)

        // constrain listingsContainerView (a stack view) to all 4 sides
        listingsContainerView.snp.makeConstraints { (make) in
            make.top.leading.bottom.trailing.equalToSuperview()
        }
    }

    func setUpListings(listings: [Listing]) {
        for listing in listings {
            let listingEntry = ListingEntry(listingId: listing.id)
            listingEntry.backgroundColor = .cyan
            listingsContainerView.addArrangedSubview(listingEntry)
        }
    }

    class ListingEntry : UIView {
        var listingId: String?
        var containerView: UIView!

        init(listingId: String) {
            super.init(frame: CGRect.zero)
            self.listingId = listingId
            self.setUpContainerView()
        }

        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }

        func setUpContainerView() {
            containerView = UIView()
            containerView.backgroundColor = .gray
            self.addSubview(containerView)

            containerView.snp.makeConstraints { (make) in
                // you want the "listing container" to be 150 x 150 pts
                make.width.equalTo(150)
                make.height.equalTo(150)
                // and it needs top and bottom constraints to give self a height value
                make.top.bottom.equalToSuperview()
                // and it needs an x-position constraint
                make.leading.equalToSuperview()
            }
        }
    }
}

I've set the "main" ListingsView background color to red ... you don't see it because its containerView subview is green and fills the view.

Each ListingEntry view has a cyan background color, and its containerView has a gray background color.

The result:

enter image description here

and Debug View Hierarchy:

enter image description here

Last notes...

  • You set your StackView .distribution = .equalSpacing but you also set .spacing = 10, which doesn't make sense.
  • If you have more ListingEntry views than will fit vertically, you'll run into problems. I'd expect you'd put that into a scroll view.
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • thank you such a great answer! Do you have any suggestions for where i can better understand the layout and constraints? – AlwaysNull Jan 31 '20 at 01:15
  • 1
    @AlwaysNull - There are thousands of articles and tutorials out there. Just do some searching and then read, read, read. I'd start with Apple's own docs: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/ – DonMag Jan 31 '20 at 13:07