Search code examples
iosswiftautolayoutsnapkit

How to put two labels horizontally by aligning their top edges in iOS?


I want to create custom view like below.

enter image description here

As you see, it consists from title and price labels. Title can have million number of lines, but its top edge should be aligned with price label. It seems simple design, but it has hundreds of solution. I tried every of them, but my title label is not growing, by having dots at the end (numberOfLines = 0 doesn't help). Here is how I approached to create such a design:

  1. I created titleLabel with top, leading, trailing to price label, bottom constraints. Also, I created price label with top and trailing constraints only in order to align their top edges. I assigned compression resistance and hugging priority to price label, because it is more important and should not be ruined. Here is code if you want:

     addSubview(titleLabel)
     addSubview(priceLabel)
     titleLabel.snp.makeConstraints { make in
         make.leading.equalToSuperview().offset(16)
         make.trailing.lessThanOrEqualTo(priceLabel.snp.leading).offset(-8)
         make.top.equalToSuperview()
         make.bottom.equalToSuperview()
     }
     priceLabel.snp.makeConstraints { make in
         make.trailing.equalToSuperview().offset(-16)
         make.top.equalTo(titleLabel.snp.top)
     }
    

I created separate custom view, because I want to use it inside StackView(spacing 8, distribution fill, vertical). Result of this approach: title label's is not growing. It has only one line with dots at the end, if it has big text.

  1. Second approach was to create stackView (horizontally, spacing 8, distribution fill, alignment top). I set alignment top in order to align top edges of the labels. The result was the as in approach #1.

How to solve this problem? Where am I wrong? It seems I don't see something core in Auto Layout theory here.


Solution

  • During development of your layout, it can be very helpful to use contrasting colors for element backgrounds... makes it really easy to see what's happening with their frames.

    Give this a try...

    Custom view class

    class NeoCustomView: UIView {
        
        let titleLabel = UILabel()
        let priceLabel = UILabel()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() -> Void {
            
            titleLabel.translatesAutoresizingMaskIntoConstraints = false
            titleLabel.numberOfLines = 0
            titleLabel.font = .systemFont(ofSize: 17)
            
            priceLabel.translatesAutoresizingMaskIntoConstraints = false
            priceLabel.font = .boldSystemFont(ofSize: 17)
            
            priceLabel.setContentHuggingPriority(.required, for: .horizontal)
            priceLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
            
            addSubview(titleLabel)
            addSubview(priceLabel)
            
            titleLabel.snp.makeConstraints { make in
                make.leading.equalToSuperview().offset(16)
                make.trailing.lessThanOrEqualTo(priceLabel.snp.leading).offset(-8)
                make.top.equalToSuperview()
                make.bottom.equalToSuperview()
            }
            priceLabel.snp.makeConstraints { make in
                make.trailing.equalToSuperview().offset(-16)
                make.top.equalTo(titleLabel.snp.top)
            }
    
            // use some background colors so we can easily see the frames
            backgroundColor = .red
            titleLabel.backgroundColor = .yellow
            priceLabel.backgroundColor = .green
            
        }
        
    }
    

    Example view controller class - adds another label constrained 4-pts from the bottom of the custom view so we can see everything working:

    class NeoViewController: UIViewController {
        
        let testView = NeoCustomView()
        let anotherLabel = UILabel()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            anotherLabel.translatesAutoresizingMaskIntoConstraints = false
            anotherLabel.font = .systemFont(ofSize: 15)
            anotherLabel.backgroundColor = .blue
            anotherLabel.textColor = .white
            anotherLabel.textAlignment = .center
            anotherLabel.numberOfLines = 0
            anotherLabel.text = "This label is constrained 4 points from the bottom of the custom view."
            
            view.addSubview(testView)
            view.addSubview(anotherLabel)
            
            testView.snp.makeConstraints { make in
                make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16)
                make.top.equalTo(view.safeAreaLayoutGuide).offset(40)
            }
            anotherLabel.snp.makeConstraints { make in
                make.top.equalTo(testView.snp.bottom).offset(4)
                make.width.equalTo(testView.snp.width)
                make.centerX.equalTo(testView.snp.centerX)
            }
            
            testView.titleLabel.text = "This is long text for the title label that will word wrap when it needs to."
            testView.priceLabel.text = "300$"
        }
    }
    

    Result (Red is custom view with title and price labels, Blue is a label added and constrained below the custom view):

    enter image description here