1

I want 2 labels (say leftLabel, rightLabel) and place them horizontally such that leftLabel stretches and rightLabel just fits single character icon (say, ">"). Thus both labels layout justified. Like this...

enter image description here

This is the code I have -

class StackViewController: UIViewController {
    /// Main vertical outer/container stack view that pins its edges to this view in storyboard (i.e. full screen)
    @IBOutlet weak private var containerStackView: UIStackView!

    private var leftLabel: UILabel = {
        let leftLabel = UILabel(frame: .zero)
        leftLabel.font = .preferredFont(forTextStyle: .body)
        leftLabel.numberOfLines = 0 // no text truncation, allows wrap
        leftLabel.backgroundColor = .orange
        return leftLabel
    }()
    private var rightLabel: UILabel = {
        let rightLabel = UILabel(frame: .zero)
        rightLabel.font = .preferredFont(forTextStyle: .body)
        // Set CHCR as high so that label sizes itself to fit the text
        rightLabel.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .horizontal)
        rightLabel.setContentCompressionResistancePriority(UILayoutPriorityDefaultHigh, for: .horizontal)
        rightLabel.backgroundColor = .green
        return rightLabel
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        prepareAndLoadSubViews()
        // Note, the text required to be set in viewDidAppear, not viewDidLoad, otherwise rightLabel stretches to fill!!
        leftLabel.text = "This is left label text that may go in multiple lines"
        rightLabel.text = ">"   // Always a single character
    }

    /// Dynamically creates a horizontal stack view, with 2 labels, in the container stack view
    private func prepareAndLoadSubViews() {
        /// Prepare the horizontal label stack view and add the 2 labels
        let labelStackView = UIStackView(arrangedSubviews: [leftLabel, rightLabel])
        labelStackView.axis = .horizontal
        labelStackView.distribution = .fillProportionally
        labelStackView.alignment = .top
        containerStackView.addArrangedSubview(labelStackView)
        containerStackView.addArrangedSubview(UIView())
    }
}

Which gives below result (i.e. leftLabel width is 0 in view debugger) -

enter image description here

NOTE: If I move text set code in viewDidAppear then it works fine.

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // Note, the text required to be set in viewDidAppear, not viewDidLoad, otherwise rightLabel stretches to fill!!
    leftLabel.text = "This is left label text that may go in multiple lines"
    rightLabel.text = ">"   // Always a single character
}

Why? And, can we set content hugging/ compression resistance priorities before viewDidLoad?

Ashok
  • 6,224
  • 2
  • 37
  • 55
  • Sorry that I'm maybe little late to the party, but have you tried increasing priority a bit `rightLabel.setContentHuggingPriority(.defaultHigh + 25, for: .horizontal)`? – Stimorol Oct 24 '19 at 09:58

1 Answers1

0

I played around with your code quite a bit but I was not able to make it work either. I think this is a bug that occurs when you add a UIStackView to another UIStackView. When you only have one UIStackView your code works fine.

So I cannot offer a fix for your case but IMHO you shouldn't really need to use a UIStackView for your 2 labels at all. UIStackView is great if you have multiple arranged subviews that you hide and show and need to be arranged automatically. For just two "static" labels I think it is a bit of an overkill.

You can achieve what you are after by adding your two labels to a UIView and then set layout constraints to the labels. It's really easy:

class StackViewController: UIViewController {

    @IBOutlet weak var containerStackView: UIStackView!

    private var leftLabel: UILabel = {
        let leftLabel = UILabel(frame: .zero)
        leftLabel.font = .preferredFont(forTextStyle: .body)
        leftLabel.numberOfLines = 0
        leftLabel.backgroundColor = .orange
        leftLabel.translatesAutoresizingMaskIntoConstraints = false
        leftLabel.numberOfLines = 0
        return leftLabel
    }()
    private var rightLabel: UILabel = {
        let rightLabel = UILabel(frame: .zero)
        rightLabel.font = .preferredFont(forTextStyle: .body)
        rightLabel.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
        rightLabel.backgroundColor = .green
        rightLabel.translatesAutoresizingMaskIntoConstraints = false
        return rightLabel
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        prepareAndLoadSubViews()
        leftLabel.text = "This is left label text that may go in multiple lines"
        rightLabel.text = ">"
    }

    private func prepareAndLoadSubViews() {
        let labelContainerView = UIView()
        labelContainerView.addSubview(leftLabel)
        labelContainerView.addSubview(rightLabel)

        NSLayoutConstraint.activate([
            leftLabel.leadingAnchor.constraint(equalTo: labelContainerView.leadingAnchor),
            leftLabel.topAnchor.constraint(equalTo: labelContainerView.topAnchor),
            leftLabel.bottomAnchor.constraint(equalTo: labelContainerView.bottomAnchor),

            rightLabel.leadingAnchor.constraint(equalTo: leftLabel.trailingAnchor),
            rightLabel.topAnchor.constraint(equalTo: labelContainerView.topAnchor),
            rightLabel.bottomAnchor.constraint(equalTo: labelContainerView.bottomAnchor),
            rightLabel.trailingAnchor.constraint(equalTo: labelContainerView.trailingAnchor)
        ])

        containerStackView.addArrangedSubview(labelContainerView)
        containerStackView.addArrangedSubview(UIView())
    }
}
joern
  • 27,354
  • 7
  • 90
  • 105
  • Thanks much for trying yourself and the advice. You are right, I can achieve the same layout using constraints. But I have additional requirements to hide rightLabel based on data i.e. leftLabel should stretch to full width. Achieving that through auto layout will require few more additional constraints. Also, overall in this cell I would have to add few more labels/imageview soon thus I would *prefer* to use stack view than constraints. – Ashok Nov 09 '17 at 14:29