Почему UIStackView с одним представлением, заполнением пропорционально и полями макета вызывает неоднозначную ошибку ограничения? - PullRequest
0 голосов
/ 08 мая 2020

Следующий код представляет собой попытку добавить UIStackView к контроллеру представления, закрепив его по всем краям с небольшим полем, и добавить к нему метку.

Я хочу, чтобы StackView использовал .fillProportionally в качестве своего режима распространения, готового к тому, когда я добавлю в него больше представлений позже. * всякий раз, когда используется режим распространения .fillProportionally и поля макета , я получаю неоднозначную ошибку ограничения (ниже). Какова причина этой ошибки?

override func viewDidLoad() {
    super.viewDidLoad()

    let label = UILabel(frame: .zero)
    label.text = "ABC"

    let stack = UIStackView(arrangedSubviews: [label])
    stack.translatesAutoresizingMaskIntoConstraints = false
    stack.distribution = .fillProportionally
    stack.isLayoutMarginsRelativeArrangement = true
    stack.layoutMargins = .init(top: 10, left: 10, bottom: 10, right: 10)
    view.addSubview(stack)

    NSLayoutConstraint.activate([
        stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        stack.widthAnchor.constraint(equalTo: view.widthAnchor),
        stack.heightAnchor.constraint(equalTo: view.heightAnchor),
    ])
}

}

Абсолютная ошибка ограничения ( WTFAutoLayout ):

(
    "<NSLayoutConstraint:0x600001a432f0 'UISV-canvas-connection' UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide'.leading == UILabel:0x7ff470913280'ABC'.leading   (active)>",
    "<NSLayoutConstraint:0x600001a423f0 'UISV-canvas-connection' UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide'.trailing == UILabel:0x7ff470913280'ABC'.trailing   (active)>",
    "<NSLayoutConstraint:0x600001a425d0 'UISV-fill-proportionally' UILabel:0x7ff470913280'ABC'.width == UIStackView:0x7ff46d510030.width   (active)>",
    "<NSLayoutConstraint:0x600001a77f70 'UIView-leftMargin-guide-constraint' H:|-(10)-[UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide'](LTR)   (active, names: '|':UIStackView:0x7ff46d510030 )>",
    "<NSLayoutConstraint:0x600001a42940 'UIView-rightMargin-guide-constraint' H:[UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide']-(10)-|(LTR)   (active, names: '|':UIStackView:0x7ff46d510030 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600001a423f0 'UISV-canvas-connection' UILayoutGuide:0x6000000f3100'UIViewLayoutMarginsGuide'.trailing == UILabel:0x7ff470913280'ABC'.trailing   (active)>

Ответы [ 2 ]

2 голосов
/ 09 мая 2020

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

Во-первых, свойство распределения .fillProportionally объекта UIStackView очень часто понимается неправильно.

Во-вторых, мы получаем некоторые странности, когда представление стека имеет вид .fillProportionally и .spacing представления стека * не 0, или когда представление стека также имеет .layoutMargins.

Проблема, с которой вы сталкиваетесь, заключается в том, как автоматическая компоновка вычисляет пропорциональный размер.

На основе экспериментов, auto-layout вычисляет пропорциональную ширину видов и , затем применяет поля макета, вычитая из ширины последнего вида, чтобы уместить пространство.

Это можно легко продемонстрировать следующим образом:

enter image description here

Имеется 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)
    }

}
0 голосов
/ 08 мая 2020

Прежде всего, установите translatesAutoresizingMaskIntoConstraints из label на false.

label.translatesAutoresizingMaskIntoConstraints = false

Поскольку вы хотите установить все layoutMargins на 10, width и height из stack не может быть равно view's width и height.

Вам необходимо учесть разницу в 20 (обе стороны) в width и height.

Таким образом, constraints должно быть

NSLayoutConstraint.activate([
    stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    stack.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1, constant: -20),
    stack.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1, constant: -20),
])

Кроме того, в ограничения включен интервал полей, нет необходимости писать приведенный ниже код. Так что удали его.

stack.isLayoutMarginsRelativeArrangement = true
stack.layoutMargins = .init(top: 10, left: 10, bottom: 10, right: 10)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...