iosswiftuiviewuistackviewprogrammatically-created

Why my 2D UIViews don't appear on screen?


I'm trying to make UIView that contains 12x7 UIViews with margins. I thought that the best way gonna be make 7 Vertical Stacks and then add all them on one big Horizontal stack. And I coded it, but problem is that this Horizontal Stacks doesn't appear on the screen at all (I've tried Xcode feature to see layers there is nothing).

This is my code:

import UIKit

class CalendarView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
    
        setupView()
    }

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

        setupView()
    }

    private func setupView() {
        // array to add in future in columnsStackView
        var columnStacks: [UIStackView] = []

        for columns in 1...12 {
            // array to add in future in columnStackView
            var columnViews: [UIView] = []

            for cell in 1...7 {
                let cellView = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
                cellView.backgroundColor = .orange
                columnViews.append(cellView)
            }

            // create columnStackView and add all 7 views
            let columnStackView = UIStackView(arrangedSubviews: columnViews)
            columnStackView.axis = .vertical
            columnStackView.distribution = .fillEqually
            columnStackView.alignment = .fill
            columnStackView.spacing = 4

            columnStacks.append(columnStackView)
        }

        // create columnsStackView and add those 12 stacks
        let columnsStackView = UIStackView(arrangedSubviews: columnStacks)
        columnsStackView.axis = .horizontal
        columnsStackView.distribution = .fillEqually
        columnsStackView.alignment = .fill
        columnsStackView.spacing = 4
        columnsStackView.translatesAutoresizingMaskIntoConstraints = false

        self.addSubview(columnsStackView)
    }
}

Can you please help me with that!!!


Solution

  • Couple things...

    A UIStackView uses auto-layout when arranging its subviews, so this line:

    let cellView = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
    

    will create a UIView, but the width and height will be ignored.

    You need to set those with constraints:

    for cell in 1...7 {
        let cellView = UIView()
        cellView.backgroundColor = .orange
                    
        // we want each "cellView" to be 24x24 points
        cellView.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
        cellView.heightAnchor.constraint(equalTo: cellView.widthAnchor).isActive = true
                    
        columnViews.append(cellView)
    }
    

    Now, because we've explicitly set the width and height of the "cellViews" we can set the stack view .distribution = .fill (instead of .fillEqually).

    Next, we have to constrain the "outer" stack view (columnsStackView) to the view itself:

    // constrain the "outer" stack view to self
    NSLayoutConstraint.activate([
        columnsStackView.topAnchor.constraint(equalTo: topAnchor),
        columnsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
        columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
        columnsStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
    ])
    

    otherwise, the view will have 0x0 dimensions.

    Here is a modified version of your class:

    class CalendarView: UIView {
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            
            setupView()
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            
            setupView()
        }
        
        private func setupView() {
            // array to add in future in columnsStackView
            var columnStacks: [UIStackView] = []
            
            for columns in 1...12 {
                // array to add in future in columnStackView
                var columnViews: [UIView] = []
                
                for cell in 1...7 {
                    let cellView = UIView()
                    cellView.backgroundColor = .orange
                    
                    // we want each "cellView" to be 24x24 points
                    cellView.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
                    cellView.heightAnchor.constraint(equalTo: cellView.widthAnchor).isActive = true
                    
                    columnViews.append(cellView)
                }
                
                // create columnStackView and add all 7 views
                let columnStackView = UIStackView(arrangedSubviews: columnViews)
                columnStackView.axis = .vertical
                columnStackView.distribution = .fill
                columnStackView.alignment = .fill
                columnStackView.spacing = 4
                
                columnStacks.append(columnStackView)
            }
            
            // create columnsStackView and add those 12 stacks
            let columnsStackView = UIStackView(arrangedSubviews: columnStacks)
            columnsStackView.axis = .horizontal
            columnsStackView.distribution = .fill
            columnsStackView.alignment = .fill
            columnsStackView.spacing = 4
            columnsStackView.translatesAutoresizingMaskIntoConstraints = false
            
            self.addSubview(columnsStackView)
            
            // constrain the "outer" stack view to self
            NSLayoutConstraint.activate([
                columnsStackView.topAnchor.constraint(equalTo: topAnchor),
                columnsStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
                columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
                columnsStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
            ])
        }
    }
    

    and a simple test controller to show how it can be used:

    class CalendarTestViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let cv = CalendarView()
            
            cv.translatesAutoresizingMaskIntoConstraints = false
            
            view.addSubview(cv)
            
            // the CalendarView will size itself, so we only need to
            //  provide x and y position constraints
            NSLayoutConstraint.activate([
                cv.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                cv.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            ])
            
            // let's give it a background color so we can see its frame
            cv.backgroundColor = .systemYellow
        }
    
    }
    

    the result:

    enter image description here