UIStackView contentHuggingPriority не работает для заполнения доступного пространства

#swift #autolayout #uistackview

#swift #автозаполнение #uistackview

Вопрос:

Я хочу создать нижнюю панель чата со следующим содержимым в горизонтальном UIStackView :

  • a UITextField с левой стороны, занимая все доступное пространство
  • метка справа с фиксированным размером (внутренний размер содержимого по умолчанию)

Вот мой код:

 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 
])
 

С помощью этого кода метка увеличивает свой размер и заполняет все доступное пространство справа, а текстовое поле не отображается (инспектор говорит width = 0); прямо противоположно тому, что я хочу.

Я не понимаю, почему это не работает, потому что я явно настроил его так, чтобы он не расширял метку с помощью:

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

и вместо этого для расширения текстового поля с:

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

Что я здесь пропустил? Я прочитал много ответов SO, документацию Apple и следил за различными учебными пособиями и видео, но я не могу добиться этой, казалось бы, простой вещи.

Комментарии:

1. Пытались ли вы оставить значения по умолчанию для представления стека, закомментировав sv.distribution = .fill и sv.alignment = .center? По умолчанию должно быть как ‘заполнить’

2. Просто попробовал, но, как вы указали, это значения по умолчанию, поэтому это не устраняет проблему :/

Ответ №1:

У вас все наоборот…

Настройка Content Hugging Priority означает» контролировать, как этот элемент охватывает его содержимое.

Итак, в качестве примера, если у вас есть a UILabel , в котором есть только буква «A», высокий приоритет охвата содержимого сохранит ширину метки только до этой единственной буквы.

Итак, с этими двумя строками:

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

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

вы указали автоматическую компоновку:

сохраняйте messageTextField только ширину текста и разрешайте label растягивать его шире, чем текст

То, что вы хотите, это:

 // don't let label stretch at all
label.setContentHuggingPriority(.required, for: .horizontal)

// let message text field stretch if needed
messageTextField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
 

Затем представление стека растянет текстовое поле, чтобы заполнить доступное пространство.

Комментарии:

1. Вот и все, большое вам спасибо! Я не являюсь носителем английского языка, я думал, что слово «объятие» означает «огромный», как «отличный», отсюда и мое замешательство!

Ответ №2:

Я быстро попробовал Xcode 12.3 на симуляторе (iPhone 12)

и оставление значений по умолчанию для stackview и обнимания содержимого сработало для меня:

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

Это результат:

скриншот

Комментарии:

1. Спасибо за ваш ответ, Лука. Это работает, но отладчик компоновки сообщает, что оба упорядоченных подвида имеют неоднозначный размер (ширина и горизонтальное положение). Это похоже на восстановление поведения UIStackView в амбициозной ситуации. Я имею в виду, что, если бы у меня было 4 представления (я хочу добавить несколько кнопок слева), как указать, чтобы одно из представлений растягивалось, а другие сжимались?