Jerem Lachkar
Jerem Lachkar

Reputation: 1207

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:

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.

Upvotes: 0

Views: 1266

Answers (2)

DonMag
DonMag

Reputation: 77691

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.

Upvotes: 1

Luca Iaco
Luca Iaco

Reputation: 3467

I tried quickly on Xcode 12.3 on Simulator (iPhone 12 )

and leaving the default values to the stackview and the content hugging, worked for me:

override func viewDidLoad() {
        super.viewDidLoad()
        
        // Just added a view anchored to the bottom of the view controller for the purpose of the test
        let bottomBar = UIView()
        bottomBar.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(bottomBar)
        bottomBar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        bottomBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        bottomBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
        bottomBar.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
        bottomBar.backgroundColor = .yellow
        
        let messageTextField = UITextField()
        messageTextField.translatesAutoresizingMaskIntoConstraints = false
        messageTextField.backgroundColor = .cyan

        // LEAVING DEFAULTS
        //messageTextField.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
        //messageTextField.setContentHuggingPriority(.defaultHigh, for: .horizontal)

        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "SEND"

        // LEAVING DEFAULTS
        //label.setContentHuggingPriority(.defaultLow, for: .horizontal)
        //label.setContentCompressionResistancePriority(.required, for: .horizontal)
        label.textColor = .red

        let sv = UIStackView()
        sv.axis = .horizontal

        // LEAVING DEFAULTS
        //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),
        ])
    }

This is the outcome:

screenshot

Upvotes: 0

Related Questions