swiftuiscrollviewuikit

Incorrect display of elements in UIScrollView


I use the scroll on the screen. At the same time, when displaying the screen on SE there are no problems, when displaying on large screens, a large gap appears between two elements. How to change this problem? I change constraints, but don't have results

import UIKit

class PlayInfoViewController: UIViewController {
    private let mainScrollView: UIScrollView = {
        let sv = UIScrollView()
        sv.backgroundColor = UIColor(named: "backgroundColor")
        sv.translatesAutoresizingMaskIntoConstraints = false
        return sv
    }()
    
    private let contentView: UIView = {
        let v = UIView()
        v.backgroundColor = UIColor(named: "backgroundColor")
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    private let horisontalStackViews: [UIStackView] = {
        var stackViews: [UIStackView] = []
        let textsForMainLabel = [
            "Draw a grid with three rows and three columns, creating nine squares in total.",
            "Players take turns placing their marker (X or O) in an empty square. To make a move, a player selects a number corresponding to the square where they want to place their marker.",
            "Player X starts by choosing a square (e.g., square 5). Player O follows by choosing an empty square (e.g., square 1). Continue alternating turns until the game ends.",
            "The first player to align three of their markers horizontally, vertically, or diagonally wins. Examples of Winning Combinations: Horizontal: Squares 1, 2, 3 or 4, 5, 6 or 7, 8, 9 Vertical: Squares 1, 4, 7 or 2, 5, 8 or 3, 6, 9 Diagonal: Squares 1, 5, 9 or 3, 5, 7"
        ]
        
        var number = 1
        for text in textsForMainLabel {
            let hStackView = UIStackView(
                arrangedSubviews: [
                    RoundView(
                        frame: CGRect.zero,
                        numberOfPosition: number
                    ),
                    RectangleView(
                        frame: CGRect.zero,
                        textForMainLabel: text
                    )
                ],
                axis: .horizontal,
                spacing: 20,
                alignment: .top)
            
            stackViews.append(hStackView)
            number += 1
        }
        
        return stackViews
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor(named: "backgroundColor")
       
        setupViews()
        setConstraints()
    }
    
    private func setupViews() {
        view.addSubview(mainScrollView)
        mainScrollView.addSubview(contentView)
        
        view.addSubview(horisontalStackViews[0])
        view.addSubview(horisontalStackViews[1])
        view.addSubview(horisontalStackViews[2])
        view.addSubview(horisontalStackViews[3])
        
        horisontalStackViews[0].translatesAutoresizingMaskIntoConstraints = false
        horisontalStackViews[1].translatesAutoresizingMaskIntoConstraints = false
        horisontalStackViews[2].translatesAutoresizingMaskIntoConstraints = false
        horisontalStackViews[3].translatesAutoresizingMaskIntoConstraints = false
        
        let hConst = contentView.heightAnchor.constraint(equalTo: mainScrollView.heightAnchor)
        hConst.isActive = true
        hConst.priority = UILayoutPriority(500)
    }
}

extension PlayInfoViewController {
    private func setConstraints() {
        NSLayoutConstraint.activate([
            mainScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            mainScrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 42),
            mainScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            mainScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
       
            contentView.leadingAnchor.constraint(equalTo: mainScrollView.leadingAnchor),
            contentView.topAnchor.constraint(equalTo: mainScrollView.topAnchor),
            contentView.trailingAnchor.constraint(equalTo: mainScrollView.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: mainScrollView.bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: mainScrollView.widthAnchor),
            //            contentView.heightAnchor.constraint(equalTo: mainScrollView.heightAnchor, multiplier: 2),
  
            horisontalStackViews[0].leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 21),
            horisontalStackViews[0].topAnchor.constraint(equalTo: contentView.topAnchor),
            horisontalStackViews[0].trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -21),
            //            horisontalStackViews[0].widthAnchor.constraint(equalTo: contentView.widthAnchor),
            //            horisontalStackViews[0].heightAnchor.constraint(equalToConstant: 75)
     
            horisontalStackViews[1].leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 21),
            horisontalStackViews[1].topAnchor.constraint(equalTo: horisontalStackViews[0].bottomAnchor, constant: 10),
            horisontalStackViews[1].trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -21),
            //            horisontalStackViews[1].widthAnchor.constraint(equalTo: contentView.widthAnchor),
            //            horisontalStackViews[1].heightAnchor.constraint(equalToConstant: 192)

            horisontalStackViews[2].leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 21),
            horisontalStackViews[2].topAnchor.constraint(equalTo: horisontalStackViews[1].bottomAnchor, constant: 10),
            horisontalStackViews[2].trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -21),
            //            horisontalStackViews[2].widthAnchor.constraint(equalTo: contentView.widthAnchor),
            //            horisontalStackViews[2].heightAnchor.constraint(equalToConstant: 171)

            horisontalStackViews[3].leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 21),
            horisontalStackViews[3].topAnchor.constraint(equalTo: horisontalStackViews[2].bottomAnchor, constant: 10),
            horisontalStackViews[3].trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -21),
            horisontalStackViews[3].bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            //            horisontalStackViews[3].widthAnchor.constraint(equalTo: contentView.widthAnchor),
            //            horisontalStackViews[3].heightAnchor.constraint(equalToConstant: 255)
        ])
    }
}

I change constraint and hConst. Don't have result

 horisontalStackViews[3].bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
let hConst = contentView.heightAnchor.constraint(equalTo: mainScrollView.heightAnchor)
        hConst.isActive = true
        hConst.priority = UILayoutPriority(500)

class RoundView: UIView {
    var numberOfPosition: Int
    
    lazy var numberLabel: UILabel = {
        let label = UILabel()
        label.text = "Some text"
        label.font = UIFont.systemFont(ofSize: 20, weight: .regular)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    required init(frame: CGRect, numberOfPosition: Int) {
        self.numberOfPosition = numberOfPosition
        super.init(frame: frame)
        
        layer.cornerRadius = 22.5
        translatesAutoresizingMaskIntoConstraints = false
        
        numberLabel.text = String(numberOfPosition)
        
        setupView()
        setConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupView() {
        backgroundColor = #colorLiteral(red: 0.8375778794, green: 0.7538908124, blue: 0.96424371, alpha: 1)
        
        addSubview(numberLabel)
    }
}

extension RoundView {
    private func setConstraints() {
        NSLayoutConstraint.activate([
            numberLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 18),
            numberLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -18),
            numberLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12),
            numberLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12)
        ])
    }
}

class RectangleView: UIView {
    var textForMainLabel: String
    
    lazy var mainLabel: UILabel = {
        let label = UILabel()
        label.text = "Some text"
        label.font = UIFont.systemFont(ofSize: 18, weight: .regular)
        label.numberOfLines = 0
        label.minimumScaleFactor = 0.5
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    required init(frame: CGRect, textForMainLabel: String) {
        self.textForMainLabel = textForMainLabel
        super.init(frame: frame)
        
        layer.cornerRadius = 30
        translatesAutoresizingMaskIntoConstraints = false
        
        mainLabel.text = textForMainLabel
        
        setupView()
        setConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupView() {
        backgroundColor = #colorLiteral(red: 0.8718322515, green: 0.8867664933, blue: 0.9465543628, alpha: 1)
        
        addSubview(mainLabel)
    }
}

extension RectangleView {
    private func setConstraints() {
        NSLayoutConstraint.activate([
            mainLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 24),
            mainLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -24),
            mainLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12),
            mainLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12)
        ])
    }
}

It's Good enter image description here

This is a wrong

enter image description here


Solution

  • Instead of constraining your horizontal stack view "rows," let's add them to a Vertical stack view.

    To avoid the "gap" between the 1st and 2nd "row" on the larger screens, we do not set a height constraint on either the vertical stack view or the "content" view. On small screens - such as the SE - we get scrolling. On larger screens, we get a bit of space at the bottom, while keeping the "row spacing" consistent.

    Looks like this:

    Result


    RoundView

    class RoundView: UIView {
        var numberOfPosition: Int
        
        lazy var numberLabel: UILabel = {
            let label = UILabel()
            label.textAlignment = .center
            label.text = "0"
            label.font = UIFont.systemFont(ofSize: 20, weight: .regular)
            label.translatesAutoresizingMaskIntoConstraints = false
            
            // make sure the label frame cannot compress
            label.setContentCompressionResistancePriority(.required, for: .horizontal)
            label.setContentCompressionResistancePriority(.required, for: .vertical)
            
            return label
        }()
        
        required init(frame: CGRect, numberOfPosition: Int) {
            self.numberOfPosition = numberOfPosition
            super.init(frame: frame)
            
            translatesAutoresizingMaskIntoConstraints = false
            
            numberLabel.text = String(numberOfPosition)
            
            setupView()
            setConstraints()
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        private func setupView() {
            backgroundColor = #colorLiteral(red: 0.8375778794, green: 0.7538908124, blue: 0.96424371, alpha: 1)
            
            addSubview(numberLabel)
        }
        override func layoutSubviews() {
            super.layoutSubviews()
            // set the cornerRadius here to make sure it matches the view size
            layer.cornerRadius = min(bounds.height, bounds.width) * 0.5
        }
    
    }
    
    extension RoundView {
        private func setConstraints() {
            NSLayoutConstraint.activate([
                numberLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 18),
                numberLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -18),
                numberLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12),
                numberLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12),
                
                // let's keep this view at a 1:1 aspect ratio
                self.widthAnchor.constraint(equalTo: self.heightAnchor),
            ])
        }
    }
    

    RectangleView

    class RectangleView: UIView {
        var textForMainLabel: String
        
        lazy var mainLabel: UILabel = {
            let label = UILabel()
            label.text = "Some text"
            label.font = UIFont.systemFont(ofSize: 18, weight: .regular)
            label.numberOfLines = 0
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }()
        
        required init(frame: CGRect, textForMainLabel: String) {
            self.textForMainLabel = textForMainLabel
            super.init(frame: frame)
            
            layer.cornerRadius = 30
            translatesAutoresizingMaskIntoConstraints = false
            
            mainLabel.text = textForMainLabel
            
            setupView()
            setConstraints()
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        private func setupView() {
            backgroundColor = #colorLiteral(red: 0.8718322515, green: 0.8867664933, blue: 0.9465543628, alpha: 1)
            
            addSubview(mainLabel)
        }
    }
    
    extension RectangleView {
        private func setConstraints() {
            NSLayoutConstraint.activate([
                mainLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 24),
                mainLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -24),
                mainLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12),
                mainLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12)
            ])
        }
    }
    

    PlayInfoViewController

    class PlayInfoViewController: UIViewController {
        private let mainScrollView: UIScrollView = {
            let sv = UIScrollView()
            sv.backgroundColor = UIColor(named: "backgroundColor")
            sv.translatesAutoresizingMaskIntoConstraints = false
            return sv
        }()
        
        private let contentView: UIView = {
            let v = UIView()
            v.backgroundColor = UIColor(named: "backgroundColor")
            v.translatesAutoresizingMaskIntoConstraints = false
            return v
        }()
        
        // vertical stack view to hold the "rows" of horizontal stack views
        private let vertStackView: UIStackView = {
            let sv = UIStackView()
            sv.axis = .vertical
            sv.spacing = 10
            sv.translatesAutoresizingMaskIntoConstraints = false
            return sv
        }()
        
        private let horisontalStackViews: [UIStackView] = {
            var stackViews: [UIStackView] = []
            let textsForMainLabel = [
                "Draw a grid with three rows and three columns, creating nine squares in total.",
                "Players take turns placing their marker (X or O) in an empty square. To make a move, a player selects a number corresponding to the square where they want to place their marker.",
                "Player X starts by choosing a square (e.g., square 5). Player O follows by choosing an empty square (e.g., square 1). Continue alternating turns until the game ends.",
                "The first player to align three of their markers horizontally, vertically, or diagonally wins. Examples of Winning Combinations: Horizontal: Squares 1, 2, 3 or 4, 5, 6 or 7, 8, 9 Vertical: Squares 1, 4, 7 or 2, 5, 8 or 3, 6, 9 Diagonal: Squares 1, 5, 9 or 3, 5, 7"
            ]
            
            var number = 1
            
            for text in textsForMainLabel {
                let hStackView = UIStackView()
                hStackView.axis = .horizontal
                hStackView.spacing = 20
                hStackView.alignment = .top
                
                let roundView = RoundView (
                    frame: CGRect.zero,
                    numberOfPosition: number
                )
                let rectView = RectangleView (
                    frame: CGRect.zero,
                    textForMainLabel: text
                )
                
                hStackView.addArrangedSubview(roundView)
                hStackView.addArrangedSubview(rectView)
                
                stackViews.append(hStackView)
                number += 1
            }
            
            return stackViews
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = UIColor(named: "backgroundColor")
            
            setupViews()
            setConstraints()
        }
        
        private func setupViews() {
            view.addSubview(mainScrollView)
            mainScrollView.addSubview(contentView)
            contentView.addSubview(vertStackView)
    
            for v in horisontalStackViews {
                vertStackView.addArrangedSubview(v)
            }
        }
    }
    
    extension PlayInfoViewController {
        private func setConstraints() {
            
            let g = view.safeAreaLayoutGuide
            let cg = mainScrollView.contentLayoutGuide
            let fg = mainScrollView.frameLayoutGuide
            
            NSLayoutConstraint.activate([
                mainScrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
                mainScrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 42),
                mainScrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
                mainScrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
                
                contentView.leadingAnchor.constraint(equalTo: cg.leadingAnchor),
                contentView.topAnchor.constraint(equalTo: cg.topAnchor),
                contentView.trailingAnchor.constraint(equalTo: cg.trailingAnchor),
                contentView.bottomAnchor.constraint(equalTo: cg.bottomAnchor),
                contentView.widthAnchor.constraint(equalTo: fg.widthAnchor),
                
                // do NOT set contentView.heightAnchor
                
                vertStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 21),
                vertStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
                vertStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -21),
                vertStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            ])
        }
    }