Home > Back-end >  How do I fix the Constraints Spacing?
How do I fix the Constraints Spacing?

Time:09-19

I am trying to create the view porgrammatically as show in image below. However the whole thing scatter once I embed the three horizontal stack view in the vertical stack view . I have set the spacing and alignment but for some reason it’s not showing the views properly when i run it in playgroung; the spacing gets messy. How do I resolve this issue ? Please ignore the pencil image. Its just there as a placeholder

enter image description here

import UIKit
import SnapKit

import Foundation
import PlaygroundSupport

extension UIStackView {
   
  func removeAllSubviews() {
    arrangedSubviews.forEach { $0.removeFromSuperview() }
  }
   
  convenience init(frame: CGRect = .zero,
           alignment: UIStackView.Alignment = .fill,
           axis: NSLayoutConstraint.Axis = .vertical,
           distribution: UIStackView.Distribution = .equalSpacing,
           spacing: CGFloat = 0.0) {
     
    self.init(frame: frame)
     
    self.alignment = alignment
    self.axis = axis
    self.distribution = distribution
    self.spacing = spacing
  }
}

class UserCell: UIView {
   
  private lazy var reputationLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 15, weight: .bold)
    label.text = "Reputation"
    return label
  }()
   
  private lazy var increasePerformaceLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 10, weight: .semibold)
    label.text = "Increase Performance"
    return label
  }()
   
   
  private lazy var increasePerformaceImage : UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.image = UIImage(systemName: "pencil.circle.fill")
    return imageView
  }()
   
  private lazy var reviewsImage : UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.image = UIImage(systemName: "pencil.circle.fill")
    return imageView
  }()
   
  private lazy var reviewsLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 13, weight: .bold)
    label.text = "Reviews"
    return label
     
  }()
  private lazy var dateLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 13, weight: .bold)
    label.text = "All Time as of 2020-10-10"
    return label
     
  }()
   
  private lazy var scoreLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
    label.textColor = .darkGray
    label.text = "325"
    return label
  }()
   
  private lazy var ratingsLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 12, weight: .semibold)
    label.textColor = .darkGray
    label.text = "Ratings"
    return label
  }()
   
   
  private lazy var ratingsNumberLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 12, weight: .semibold)
    label.textColor = .darkGray
    label.text = "4.5"
    return label
  }()
   
  private lazy var ratingStarImage : UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.image = UIImage(systemName: "pencil.circle.fill")
    return imageView
  }()
   
   
  override init(frame: CGRect) {
    super.init(frame: frame)
    addComponents()
  }
   
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
     
  }
   
   
  private func addComponents() {
     
     
    let reputationStackView = UIStackView(frame: .zero, alignment: .top, axis: .horizontal, distribution: .equalCentering, spacing:25)
     
    let reviewStackView = UIStackView(frame: .zero, alignment: .top, axis: .horizontal, distribution: .equalCentering, spacing: 25)
     
     
    let ratingStackView = UIStackView(frame: .zero, alignment: .top, axis: .horizontal, distribution: .equalSpacing, spacing: 20)
     
    let dateStackView = UIStackView(frame: .zero, alignment: .top, axis: .horizontal, distribution: .equalSpacing, spacing: 20)
     
     
    let verticalStackView = UIStackView(frame: .zero, alignment: .top, axis: .vertical, distribution: .fill, spacing: 9)
     
     
    reputationStackView.addArrangedSubview(reputationLabel)
    reputationStackView.addArrangedSubview(increasePerformaceLabel)
    reputationStackView.addArrangedSubview(increasePerformaceImage)
    
    reviewStackView.addArrangedSubview(reviewsImage)
    reviewStackView.addArrangedSubview(reviewsLabel)
    reviewStackView.addArrangedSubview(scoreLabel)
     
    dateStackView.addArrangedSubview(dateLabel)

     
    ratingStackView.addArrangedSubview(ratingsLabel)
    ratingStackView.addArrangedSubview(ratingsNumberLabel)
    ratingStackView.addArrangedSubview(ratingStarImage)
     
    verticalStackView.addArrangedSubview(reputationStackView)
    verticalStackView.addArrangedSubview(reviewStackView)
    verticalStackView.addArrangedSubview(dateStackView)
    verticalStackView.addArrangedSubview(ratingStackView)
     
    addSubview(verticalStackView)
     
    increasePerformaceImage.snp.makeConstraints { (make) in
      make.width.height.equalTo(20)
    }
    reviewsImage.snp.makeConstraints { (make) in
      make.width.height.equalTo(20)
    }
    ratingStarImage.snp.makeConstraints { (make) in
      make.width.height.equalTo(20)
    }
     
    verticalStackView.snp.makeConstraints { (make) in
          make.edges.equalToSuperview()
        }
  }
}


let userCell = UserCell(frame: CGRect(x: 0, y: 0, width: 400, height: 100))
PlaygroundPage.current.liveView = userCell

CodePudding user response:

As I mentioned in my comment... during layout development it can be a great help to give UI elements contrasting background colors to make it easy to see their frames at run-time.

Also, it really, really, REALLY helps to add comments in your code, so you know what you expect to happen.

Take a look at this modification to your UserCell class:

class UserCell: UIView {
    
    private lazy var reputationLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 15, weight: .bold)
        label.text = "Reputation"
        return label
    }()
    
    private lazy var increasePerformaceLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 10, weight: .semibold)
        label.textColor = .gray
        label.text = "Increased Performance"
        return label
    }()
    
    private lazy var increasePerformaceImage : UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(systemName: "pencil.circle.fill")
        return imageView
    }()
    
    private lazy var reviewsImage : UIImageView = {
        let imageView = UIImageView()
        imageView.image = UIImage(systemName: "pencil.circle.fill")
        return imageView
    }()
    
    private lazy var reviewsLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 13, weight: .bold)
        label.text = "Reviews"
        return label
    }()
    
    private lazy var dateLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 13, weight: .bold)
        label.textColor = .gray
        label.text = "All Time as of 2020-10-10"
        return label
    }()
    
    private lazy var scoreLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 28, weight: .semibold)
        label.textColor = .darkGray
        label.text = "325"
        return label
    }()
    
    private lazy var ratingsLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 12, weight: .semibold)
        label.textColor = .darkGray
        label.text = "Rating"
        return label
    }()
    
    private lazy var ratingsNumberLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 12, weight: .semibold)
        label.textColor = .darkGray
        label.text = "4.5"
        return label
    }()
    
    private var colorBackgrounds: Bool = false
    convenience init(debug: Bool) {
        self.init(frame: .zero)
        self.colorBackgrounds = debug
        addComponents()
    }
    
    private func addComponents() {
        
        // horizontal stack view for the "top row" - alignment is Vertical Centering
        let repStack = UIStackView(frame: .zero, alignment: .center, axis: .horizontal, distribution: .fill, spacing: 8.0)
        repStack.addArrangedSubview(reputationLabel)
        repStack.addArrangedSubview(increasePerformaceLabel)
        repStack.addArrangedSubview(increasePerformaceImage)
        
        // increasePerformaceLabel needs right alignment
        increasePerformaceLabel.textAlignment = .right

        // increasePerformaceImage 20x20
        increasePerformaceImage.snp.makeConstraints { (make) in
            make.width.height.equalTo(20.0)
        }

        // horizontal stack view for the "middle row" - alignment is Vertical Centering
        let revStack = UIStackView(frame: .zero, alignment: .center, axis: .horizontal, distribution: .fill, spacing: 8.0)
        // we'll use a vertical "sub stack" for the left-side of this "row" - alignment is Leading
        let revLeftStack = UIStackView(frame: .zero, alignment: .leading, axis: .vertical, distribution: .fill, spacing: 2.0)
        // we'll use a horizontal "sub stack" for the "top line" of the "left side" - alignment is Vertical Centering
        let revLeftTopStack = UIStackView(frame: .zero, alignment: .center, axis: .horizontal, distribution: .fill, spacing: 4.0)
        
        revLeftTopStack.addArrangedSubview(reviewsImage)
        revLeftTopStack.addArrangedSubview(reviewsLabel)
        
        revLeftStack.addArrangedSubview(revLeftTopStack)
        revLeftStack.addArrangedSubview(dateLabel)
        
        revStack.addArrangedSubview(revLeftStack)
        revStack.addArrangedSubview(scoreLabel)
        
        // reviewsImage 20x20
        reviewsImage.snp.makeConstraints { (make) in
            make.width.height.equalTo(20.0)
        }
        
        // horizontal stack view for the "bottom row" - alignment is Vertical Centering
        let ratStack = UIStackView(frame: .zero, alignment: .center, axis: .horizontal, distribution: .fill, spacing: 8.0)
        // horizontal stack view for the stars - alignment is Vertical Centering
        let starStack = UIStackView(frame: .zero, alignment: .center, axis: .horizontal, distribution: .fill, spacing: 2.0)

        ratStack.addArrangedSubview(ratingsLabel)
        ratStack.addArrangedSubview(ratingsNumberLabel)
        ratStack.addArrangedSubview(starStack)
        
        // just for example, 4 filled stars and 1 half-filled star
        for _ in 1...4 {
            if let img = UIImage(systemName: "star.fill") {
                let v = UIImageView(image: img)
                v.snp.makeConstraints { (make) in
                    make.height.equalTo(16.0)
                    make.width.equalTo(v.snp.height).multipliedBy(1.2)
                }
                starStack.addArrangedSubview(v)
            }
        }
        for _ in 5...5 {
            if let img = UIImage(systemName: "star.lefthalf.fill") {
                let v = UIImageView(image: img)
                v.snp.makeConstraints { (make) in
                    make.height.equalTo(16.0)
                    make.width.equalTo(v.snp.height).multipliedBy(1.2)
                }
                starStack.addArrangedSubview(v)
            }
        }

        // we want two "separator" lines
        let sepLineViewA = UIView()
        sepLineViewA.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        let sepLineViewB = UIView()
        sepLineViewB.backgroundColor = sepLineViewA.backgroundColor

        // vertical stack view to hold the "rows" and sep lines
        let vs = UIStackView(frame: .zero, alignment: .center, axis: .vertical, distribution: .fill, spacing: 8.0)
        
        vs.addArrangedSubview(repStack)
        vs.addArrangedSubview(sepLineViewA)
        vs.addArrangedSubview(revStack)
        vs.addArrangedSubview(sepLineViewB)
        vs.addArrangedSubview(ratStack)

        // add vertical stack view to self
        addSubview(vs)
        
        // let's add a little top and bottom "padding"
        //  and fill the width
        vs.snp.makeConstraints { (make) in
            make.top.bottom.equalToSuperview().inset(8.0)
            make.leading.trailing.equalToSuperview()
        }
        
        // set widths for "row" stack views and sep line views
        repStack.snp.makeConstraints { (make) in
            make.width.equalToSuperview().inset(16.0)
        }
        // top separator line extends full width
        sepLineViewA.snp.makeConstraints { (make) in
            make.width.equalToSuperview()
        }
        revStack.snp.makeConstraints { (make) in
            make.width.equalTo(repStack.snp.width)
        }
        sepLineViewB.snp.makeConstraints { (make) in
            make.width.equalTo(repStack.snp.width)
        }
        ratStack.snp.makeConstraints { (make) in
            make.width.equalTo(repStack.snp.width)
        }

        // "row" stack views and sep line views heights
        //  Note: we do NOT set a height for the Reputation stack view
        
        // this will make all three stack views the same heights
        revStack.snp.makeConstraints { (make) in
            make.height.equalTo(repStack.snp.height)
        }
        ratStack.snp.makeConstraints { (make) in
            make.height.equalTo(repStack.snp.height)
        }
        
        // sep line views heights
        sepLineViewA.snp.makeConstraints { (make) in
            make.height.equalTo(1.0)
        }
        sepLineViewB.snp.makeConstraints { (make) in
            make.height.equalTo(1.0)
        }

        if colorBackgrounds {
            repStack.backgroundColor = UIColor(red: 1.0, green: 0.9, blue: 0.9, alpha: 1.0)
            revStack.backgroundColor = UIColor(red: 0.9, green: 1.0, blue: 0.9, alpha: 1.0)
            ratStack.backgroundColor = UIColor(red: 0.9, green: 0.9, blue: 1.0, alpha: 1.0)
            
            reputationLabel.backgroundColor = .yellow
            increasePerformaceLabel.backgroundColor = .cyan
            reviewsLabel.backgroundColor = .green
            dateLabel.backgroundColor = .yellow
            scoreLabel.backgroundColor = .cyan
            ratingsLabel.backgroundColor = .green
            ratingsNumberLabel.backgroundColor = .yellow
        }

    }
    
}

extension UIStackView {
    
    func removeAllSubviews() {
        arrangedSubviews.forEach { $0.removeFromSuperview() }
    }
    
    convenience init(frame: CGRect = .zero,
                     alignment: UIStackView.Alignment = .fill,
                     axis: NSLayoutConstraint.Axis = .vertical,
                     distribution: UIStackView.Distribution = .equalSpacing,
                     spacing: CGFloat = 0.0) {
        
        self.init(frame: frame)
        
        self.alignment = alignment
        self.axis = axis
        self.distribution = distribution
        self.spacing = spacing
    }
}

and an example controller - we'll create Two instances, showing the "debug" colors in the second one:

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        
        let testView = UserCell(debug: false)
        testView.backgroundColor = .white
        view.addSubview(testView)

        let testViewD = UserCell(debug: true)
        testViewD.backgroundColor = .white
        view.addSubview(testViewD)

        let g = view.safeAreaLayoutGuide
        
        testView.snp.makeConstraints { (make) in
            make.top.leading.trailing.equalTo(g).inset(16.0)
        }
        
        testViewD.snp.makeConstraints { (make) in
            make.top.equalTo(testView.snp.bottom).offset(20.0)
            make.leading.trailing.equalTo(testView)
        }
        
        [testView, testViewD].forEach { v in
            v.layer.cornerRadius = 8.0
            v.layer.shadowColor = UIColor.black.cgColor
            v.layer.shadowRadius = 3.0
            v.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
            v.layer.shadowOpacity = 0.2
        }
        
    }
    
}

and here's the output:

enter image description here

  • Related