Редактирование с уточнением, поскольку мой первоначальный ответ был не совсем правильным ...
Во-первых, свойство распределения .fillProportionally
объекта UIStackView
очень часто понимается неправильно.
Во-вторых, мы получаем некоторые странности, когда представление стека имеет вид .fillProportionally
и .spacing
представления стека * не 0
, или когда представление стека также имеет .layoutMargins
.
Проблема, с которой вы сталкиваетесь, заключается в том, как автоматическая компоновка вычисляет пропорциональный размер.
На основе экспериментов, auto-layout вычисляет пропорциональную ширину видов и , затем применяет поля макета, вычитая из ширины последнего вида, чтобы уместить пространство.
Это можно легко продемонстрировать следующим образом:
Имеется 6 горизонтальных стековых представлений, каждое из которых имеет ширину 200 точек, набор распределения до .fillProportionally
и заполнен одним или двумя видами. Красные представления имеют внутреннюю c ширину 25, зеленые представления 75.
Первое представление стека, с одним видом и без полей макета, заполняет ширину, как и ожидалось ... красное представление занимает до 100% пространства.
Второй вид стека, с двумя видами и без полей макета, также заполняется, как ожидалось ... красный вид имеет ширину 50 пунктов (25%), а зеленый вид - 150 точек в ширину (75%).
Однако третье представление стека начинает показывать проблему. Единичный вид дает пропорциональную ширину 100% или 200 пунктов ... но затем применяются поля макета. Это сдвигает вид на 10 пунктов влево, но поскольку автоматическая компоновка не вычитает пространство из первого подпредставления, оно фактически расширяется на 10 пунктов за край представления стека (так что красный вид по-прежнему 200 точек в ширину).
Четвертый вид стопки выглядит так, как будто он делает то, что мы хотим ... пропорциональная заливка с полем по 10 точек с каждой стороны ... красный вид имеет ширину 50 пунктов (25% от 200) , но зеленый вид имеет ширину только 130 пунктов. Таким образом, автоматическая компоновка дала двум представлениям 50 пунктов (25%) и 150 пунктов (75%), но , затем он применил поля и убрал 20 пунктов с Зеленый вид.
Использование полей макета left: 100 right: 0
или left: 0 right: 100
для двух нижних видов стека делает это намного более очевидным. Опять же, для каждого из них красный получает 50 очков (25%), а зеленый - 150 очков (75%), но затем запас в 100 очков удаляется из зеленого.
Итак. , чтобы ответить на исходный вопрос о том, почему мы получаем неоднозначные ограничения, когда у нас есть одно упорядоченное подпредставление и поля макета, посмотрите на представление стека 3. Авто-макет не смог дать красный 100% пространства и применяют поля, поэтому возникает ошибка макета.
Вот код для запуска приведенного выше примера. Если вы закомментируете третье представление стека, вы не получите ошибку:
class ProportionalStackExampleViewController: UIViewController {
let outerStackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.spacing = 8
return v
}()
let outerStackFrame: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.layer.borderWidth = 0.5
v.layer.borderColor = UIColor.blue.cgColor
return v
}()
let infoLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.font = UIFont.systemFont(ofSize: 12.0, weight: .light)
v.numberOfLines = 0
v.textAlignment = .center
v.text = "Red views have intrinsic width of 25\nGreen views have intrinsic width of 75\nAll horizontal stack views are 200-pts wide\nTap any view to see its width"
return v
}()
let sizeLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.text = "(width)"
return v
}()
let myGreen = UIColor(red: 0, green: 0.75, blue: 0, alpha: 1.0)
override func viewDidLoad() {
super.viewDidLoad()
for _ in 1...6 {
let lbl = UILabel()
lbl.font = UIFont.systemFont(ofSize: 12.0, weight: .light)
lbl.numberOfLines = 0
lbl.textAlignment = .center
outerStackView.addArrangedSubview(lbl)
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .horizontal
sv.distribution = .fillProportionally
sv.spacing = 0
outerStackView.addArrangedSubview(sv)
}
view.addSubview(infoLabel)
view.addSubview(sizeLabel)
view.addSubview(outerStackFrame)
view.addSubview(outerStackView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
infoLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
sizeLabel.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20.0),
sizeLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
outerStackView.topAnchor.constraint(equalTo: sizeLabel.bottomAnchor, constant: 20.0),
outerStackView.widthAnchor.constraint(equalToConstant: 200.0),
outerStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
outerStackFrame.widthAnchor.constraint(equalTo: outerStackView.widthAnchor),
outerStackFrame.heightAnchor.constraint(equalTo: outerStackView.heightAnchor),
outerStackFrame.centerXAnchor.constraint(equalTo: outerStackView.centerXAnchor),
outerStackFrame.centerYAnchor.constraint(equalTo: outerStackView.centerYAnchor),
])
// StackView 1
if let lbl = outerStackView.arrangedSubviews[0] as? UILabel,
let sv = outerStackView.arrangedSubviews[1] as? UIStackView {
lbl.text = "One view, no layoutMargins"
var v = ProportionalView()
v.w = 25.0
v.backgroundColor = .red
sv.addArrangedSubview(v)
var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
}
// StackView 2
if let lbl = outerStackView.arrangedSubviews[2] as? UILabel,
let sv = outerStackView.arrangedSubviews[3] as? UIStackView {
lbl.text = "Two views, no layoutMargins"
var v = ProportionalView()
v.w = 25.0
v.backgroundColor = .red
sv.addArrangedSubview(v)
var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
v = ProportionalView()
v.w = 75.0
v.backgroundColor = myGreen
sv.addArrangedSubview(v)
tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
}
// comment out this block to see the auto-layout error goes away
// StackView 3
if let lbl = outerStackView.arrangedSubviews[4] as? UILabel,
let sv = outerStackView.arrangedSubviews[5] as? UIStackView {
lbl.text = "One view\nlayoutMargins left: 10 right: 10"
var v = ProportionalView()
v.w = 25.0
v.backgroundColor = .red
sv.addArrangedSubview(v)
sv.isLayoutMarginsRelativeArrangement = true
sv.layoutMargins = .init(top: 0, left: 10, bottom: 0, right: 10)
var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
}
// StackView 4
if let lbl = outerStackView.arrangedSubviews[6] as? UILabel,
let sv = outerStackView.arrangedSubviews[7] as? UIStackView {
lbl.text = "Two views\nlayoutMargins left: 10 right: 10"
var v = ProportionalView()
v.w = 25.0
v.backgroundColor = .red
sv.addArrangedSubview(v)
var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
v = ProportionalView()
v.w = 75.0
v.backgroundColor = myGreen
sv.addArrangedSubview(v)
sv.isLayoutMarginsRelativeArrangement = true
sv.layoutMargins = .init(top: 0, left: 10, bottom: 0, right: 10)
tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
}
// StackView 5
if let lbl = outerStackView.arrangedSubviews[8] as? UILabel,
let sv = outerStackView.arrangedSubviews[9] as? UIStackView {
lbl.text = "layoutMargins left: 100 right: 0"
var v = ProportionalView()
v.w = 25.0
v.backgroundColor = .red
sv.addArrangedSubview(v)
var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
v = ProportionalView()
v.w = 75.0
v.backgroundColor = myGreen
sv.addArrangedSubview(v)
sv.isLayoutMarginsRelativeArrangement = true
sv.layoutMargins = .init(top: 0, left: 100, bottom: 0, right: 0)
tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
}
// StackView 6
if let lbl = outerStackView.arrangedSubviews[10] as? UILabel,
let sv = outerStackView.arrangedSubviews[11] as? UIStackView {
lbl.text = "layoutMargins left: 0 right: 100"
var v = ProportionalView()
v.w = 25.0
v.backgroundColor = .red
sv.addArrangedSubview(v)
var tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
v = ProportionalView()
v.w = 75.0
v.backgroundColor = myGreen
sv.addArrangedSubview(v)
sv.isLayoutMarginsRelativeArrangement = true
sv.layoutMargins = .init(top: 0, left: 0, bottom: 0, right: 100)
tg = UITapGestureRecognizer(target: self, action: #selector(showWidth(_:)))
v.addGestureRecognizer(tg)
}
}
@objc func showWidth(_ sender: UITapGestureRecognizer) -> Void {
if let v = sender.view {
sizeLabel.text = "Width: \(v.frame.width)"
sizeLabel.textColor = v.backgroundColor
}
}
}
class ProportionalView: UIView {
var w: CGFloat = 1.0
override var intrinsicContentSize: CGSize {
return CGSize(width: w, height: 40.0)
}
}