Search code examples
swiftautolayoutuistackview

UIStackView contentHuggingPriority doesn't work for filling available space


I want to create a chat bottom bar with the following content, in a horizontal UIStackView:

  • a UITextField on the left side, taking all the available space
  • a label on the right with fixed size (default intrinsic content size)

Here is my code:

let messageTextField = UITextField()
messageTextField.translatesAutoresizingMaskIntoConstraints = false
messageTextField.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
messageTextField.setContentHuggingPriority(.defaultHigh, for: .horizontal)

let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "SEND"
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
label.setContentCompressionResistancePriority(.required, for: .horizontal)
label.textColor = .red

let sv = UIStackView()
sv.axis = .horizontal
sv.distribution = .fill
sv.alignment = .center
sv.translatesAutoresizingMaskIntoConstraints = false

// constraint the stack view to the bottomBar view 
bottomBar.addSubview(sv)
NSLayoutConstraint.activate([
    sv.leadingAnchor.constraint(equalTo: bottomBar.leadingAnchor, constant: 20),
    sv.trailingAnchor.constraint(equalTo: bottomBar.trailingAnchor, constant: -20),
    sv.bottomAnchor.constraint(equalTo: bottomBar.bottomAnchor, constant: -8),
    sv.topAnchor.constraint(equalTo: bottomBar.topAnchor, constant: 8)
])

sv.addArrangedSubview(messageTextField)
sv.addArrangedSubview(label)

NSLayoutConstraint.activate([
    messageTextField.heightAnchor.constraint(equalTo:sv.heightAnchor),
    // I didn't put any width constraint for textField because it make no sense, as I want it to take all the available space on the right 
])

With this code, the label is expanding its size and filling all the available space on the right, and the text field is not showing (inspector says width = 0); exactly the opposite of what I want.

I don't understand why it doesn't work because I explicitly configured it so it does not expand the label with:

label.setContentHuggingPriority(.defaultLow, for: .horizontal)

and instead to expand the text field with:

messageTextField.setContentHuggingPriority(.defaultHigh, for: .horizontal)

What did I miss here? I have read through many SO answers, Apple documentation, and followed various tutorials and videos, but I'm not able to achieve this seemingly simple thing.


Solution

  • You have it backwards...

    Setting Content Hugging Priority means" control how this element hugs its content.

    So, as an example, if you have a UILabel with only the letter "A" in it, a High content hugging priority will keep the label only as wide as that single letter.

    So with these two lines:

    label.setContentHuggingPriority(.defaultLow, for: .horizontal)
    
    messageTextField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
    

    you've told auto-layout:

    keep messageTextField only as wide as its text, and allow label to stretch wider than its text

    What you want is:

    // don't let label stretch at all
    label.setContentHuggingPriority(.required, for: .horizontal)
    
    // let message text field stretch if needed
    messageTextField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
    

    The stack view will then stretch the text field to fill the available space.