При изменении набора признаков возникают конфликты ограничений, как будто ось стека не изменилась - PullRequest
0 голосов
/ 28 января 2019

У меня есть стек с двумя элементами управления.

Когда пользовательский интерфейс не имеет вертикальных ограничений: Вертикальный1

Когда пользовательский интерфейс имеет вертикальных ограничений: Горизонтальный1

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

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

class ViewController: UIViewController {

    var rootStack: UIStackView!
    var aggregateStack: UIStackView!
    var field1: UITextField!
    var field2: UITextField!
    var f1f2TrailTrail: NSLayoutConstraint!

    override func viewDidLoad() {

        super.viewDidLoad()
        view.backgroundColor = .white
        createIntializeViews()
        createInitializeAddStacks()
    }

    private func createIntializeViews() {

        field1 = UITextField()
        field2 = UITextField()
        field1.text = "test 1"
        field2.text = "test 2"
    }

    private func createInitializeAddStacks() {

        rootStack = UIStackView()             

        aggregateStack = UIStackView()

        // If I comment out the following, there are no constraint conflicts
        aggregateStack.spacing = 2            

        aggregateStack.addArrangedSubview(field1)
        aggregateStack.addArrangedSubview(field2)
        rootStack.addArrangedSubview(aggregateStack)

        view.addSubview(rootStack)

        rootStack.translatesAutoresizingMaskIntoConstraints = false
        aggregateStack.translatesAutoresizingMaskIntoConstraints = false
        field1.translatesAutoresizingMaskIntoConstraints = false
        field2.translatesAutoresizingMaskIntoConstraints = false

        f1f2TrailTrail = field2.trailingAnchor.constraint(equalTo: field1.trailingAnchor)
    }


    override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

        super.traitCollectionDidChange(previousTraitCollection)

        if traitCollection.verticalSizeClass == .regular {
            aggregateStack.axis = .vertical
            f1f2TrailTrail.isActive = true
        } else if traitCollection.verticalSizeClass == .compact {
            f1f2TrailTrail.isActive = false
            aggregateStack.axis = .horizontal
        } else {
            print("Unexpected")
        }
    }
}

Конфликты ограничений здесь -

(
    "<NSLayoutConstraint:0x600001e7d1d0 UITextField:0x7f80b2035000.trailing == UITextField:0x7f80b201d000.trailing   (active)>",
    "<NSLayoutConstraint:0x600001e42800 'UISV-spacing' H:[UITextField:0x7f80b201d000]-(2)-[UITextField:0x7f80b2035000]   (active)>"
)

Will attempt to recover by breaking constraint 
    <NSLayoutConstraint:0x600001e42800 'UISV-spacing' H:[UITextField:0x7f80b201d000]-(2)-[UITextField:0x7f80b2035000]   (active)>

Когда я помещаю вывод в www.wtfautolayout.com, я получаю следующее: Проще читать вывод

Второе ограничение, показанное на изображении выше, заставляет меня думать, что изменение вертикальной оси стека не произошло до того, как были оценены ограничения.

Может кто-нибудь сказать мне, что я сделал неправильно или какдля правильной настройки (предпочтительно без раскадровки)?

[РЕДАКТИРОВАТЬ] Текстовые поля выравнивают задний край, чтобы иметь это:

Больше формы - портрет

Больше формы - пейзаж

Ответы [ 2 ]

0 голосов
/ 01 февраля 2019

Пара примечаний ...

  • Существует внутренняя проблема с «вложенными» представлениями стека, вызывающая конфликты ограничений.Этого можно избежать, установив приоритет для затронутых элементов равным 999 (вместо значения по умолчанию 1000).
  • Ваш макет становится немного сложным ... Метки "прикреплены" к текстовым полям;элементы должны находиться на двух «линиях» в портретной ориентации или одной «линии» в альбомной ориентации;один элемент «многоэлементной линии», имеющий разную высоту (степпер);и т. д.
  • Чтобы ваши поля "field2" и "field3" были равны по размеру, вам нужно ограничить их ширину до , даже если они не являются подпредставлениями одного и того же подпредставления .Это совершенно верно, если они являются потомками одной и той же иерархии представлений.
  • Stackviews великолепны - за исключением случаев, когда это не так.Я бы почти предложил использовать только ограничения.Вам нужно добавить больше ограничений, но это может избежать некоторых проблем с представлениями стека.

Однако вот пример, который должен помочь вам в этом.

Я добавил подкласс UIStackView с именем LabeledFieldStackView ... он устанавливает метку над полем в виде стека.Несколько чище, чем смешивать его с другим кодом макета.

class LabeledFieldStackView: UIStackView {

    var theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    var theField: UITextField = {
        let v = UITextField()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.borderStyle = .roundedRect
        return v
    }()

    convenience init(with labelText: String, fieldText: String, verticalGap: CGFloat) {

        self.init()

        axis = .vertical
        alignment = .fill
        distribution = .fill
        spacing = 2

        addArrangedSubview(theLabel)
        addArrangedSubview(theField)

        theLabel.text = labelText
        theField.text = fieldText

        self.translatesAutoresizingMaskIntoConstraints = false

    }

}

class LargentViewController: UIViewController {

    var rootStack: UIStackView!

    var fieldStackView1: LabeledFieldStackView!
    var fieldStackView2: LabeledFieldStackView!
    var fieldStackView3: LabeledFieldStackView!
    var fieldStackView4: LabeledFieldStackView!

    var stepper: UIStepper!

    var fieldAndStepperStack: UIStackView!

    var twoLineStack: UIStackView!

    var fieldAndStepperStackWidthConstraint: NSLayoutConstraint!

    // horizontal gap between elements on the same "line"
    var horizontalSpacing: CGFloat!

    // vertical gap between "lines"
    var verticalSpacing: CGFloat!

    // vertical gap between labels above text fields
    var labelToFieldSpacing: CGFloat!

    override func viewDidLoad() {

        super.viewDidLoad()

        view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)

        horizontalSpacing = CGFloat(2)
        verticalSpacing = CGFloat(8)
        labelToFieldSpacing = CGFloat(2)

        createIntializeViews()
        createInitializeStacks()
        fillStacks()

    }

    private func createIntializeViews() {

        fieldStackView1 = LabeledFieldStackView(with: "label 1", fieldText: "field 1", verticalGap: labelToFieldSpacing)
        fieldStackView2 = LabeledFieldStackView(with: "label 2", fieldText: "field 2", verticalGap: labelToFieldSpacing)
        fieldStackView3 = LabeledFieldStackView(with: "label 3", fieldText: "field 3", verticalGap: labelToFieldSpacing)
        fieldStackView4 = LabeledFieldStackView(with: "label 4", fieldText: "field 4", verticalGap: labelToFieldSpacing)

        stepper = UIStepper()

    }

    private func createInitializeStacks() {

        rootStack = UIStackView()
        fieldAndStepperStack = UIStackView()
        twoLineStack = UIStackView()

        [rootStack, fieldAndStepperStack, twoLineStack].forEach {
            $0?.translatesAutoresizingMaskIntoConstraints = false
        }

        // rootStack has spacing of horizontalSpacing (inter-line vertical spacing)
        rootStack.axis = .vertical
        rootStack.alignment = .fill
        rootStack.distribution = .fill
        rootStack.spacing = verticalSpacing

        // fieldAndStepperStack has spacing of horizontalSpacing (space between field and stepper)
        // and .alignment of .bottom (so stepper aligns vertically with field)
        fieldAndStepperStack.axis = .horizontal
        fieldAndStepperStack.alignment = .bottom
        fieldAndStepperStack.distribution = .fill
        fieldAndStepperStack.spacing = horizontalSpacing

        // twoLineStack has inter-line vertical spacing of
        //   verticalSpacing in portrait orientation
        // for landscape orientation, the two "lines" will be changed to one "line"
        //  and the spacing will be changed to horizontalSpacing
        twoLineStack.axis = .vertical
        twoLineStack.alignment = .leading
        twoLineStack.distribution = .fill
        twoLineStack.spacing = verticalSpacing

    }

    private func fillStacks() {

        self.view.addSubview(rootStack)

        // constrain rootStack Top, Leading, Trailing = 20
        // no height or bottom constraint
        NSLayoutConstraint.activate([
            rootStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
            rootStack.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
            rootStack.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),
            ])

        rootStack.addArrangedSubview(fieldStackView1)

        fieldAndStepperStack.addArrangedSubview(fieldStackView2)
        fieldAndStepperStack.addArrangedSubview(stepper)

        twoLineStack.addArrangedSubview(fieldAndStepperStack)
        twoLineStack.addArrangedSubview(fieldStackView3)

        rootStack.addArrangedSubview(twoLineStack)

        // fieldAndStepperStack needs width constrained to its superview (the twoLineStack) when
        //  in portrait orientation
        // setting the priority to 999 prevents "nested stackView" constraint breaks
        fieldAndStepperStackWidthConstraint = fieldAndStepperStack.widthAnchor.constraint(equalTo: twoLineStack.widthAnchor, multiplier: 1.0)
        fieldAndStepperStackWidthConstraint.priority = UILayoutPriority(rawValue: 999)

        // constrain fieldView3 width to fieldView2 width to keep them the same size
        NSLayoutConstraint.activate([
            fieldStackView3.widthAnchor.constraint(equalTo: fieldStackView2.widthAnchor, multiplier: 1.0)
            ])

        rootStack.addArrangedSubview(fieldStackView4)

    }

    override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

        super.traitCollectionDidChange(previousTraitCollection)

        if traitCollection.verticalSizeClass == .regular {
            fieldAndStepperStackWidthConstraint.isActive = true
            twoLineStack.axis = .vertical
            twoLineStack.spacing = verticalSpacing
        } else if traitCollection.verticalSizeClass == .compact {
            fieldAndStepperStackWidthConstraint.isActive = false
            twoLineStack.axis = .horizontal
            twoLineStack.spacing = horizontalSpacing
        } else {
            print("Unexpected")
        }
    }

}

И результаты:

enter image description here

enter image description here

0 голосов
/ 29 января 2019

Когда UIView добавляется к UIStackView, stackView назначит ограничения для этого представления на основе свойств, назначенных stackView (axis, alignment, distribution, spacing).Как уже упоминалось @DonMag, вы добавляете ограничение к textField в представлении aggregateStack.aggregateStack добавит свои собственные ограничения на основе его атрибутов.При удалении этого ограничения и кода активации / деактивации конфликт ограничений исчезает.

Я создал небольшой пример, используя ваш код и добавив некоторые фоновые представления в stackViews, чтобы вы могли легче видеть, что происходит, когда вы изменяете различные свойства.Просто для иллюстрации я прикрепил rootStackView к краям вида контроллера представления, чтобы он был виден.

import UIKit

class StackViewController: UIViewController {

    var rootStack: UIStackView!
    var aggregateStack: UIStackView!
    var field1: UITextField!
    var field2: UITextField!
    var f1f2TrailTrail: NSLayoutConstraint!

    private lazy var backgroundView: UIView = {
        let view = UIView()
        view.backgroundColor = .purple
        view.layer.cornerRadius = 10.0
        return view
    }()

    private lazy var otherBackgroundView: UIView = {
        let view = UIView()
        view.backgroundColor = .green
        view.layer.cornerRadius = 10.0
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        createIntializeViews()
        createInitializeAddStacks()
    }

    private func createIntializeViews() {

        field1 = UITextField()
        field1.backgroundColor = .orange
        field2 = UITextField()
        field2.backgroundColor = .blue
        field1.text = "test 1"
        field2.text = "test 2"
    }

    private func createInitializeAddStacks() {

        rootStack = UIStackView()
        rootStack.alignment = .center
        rootStack.distribution = .fillProportionally
        pinBackground(backgroundView, to: rootStack)

        aggregateStack = UIStackView()
        aggregateStack.alignment = .center
        aggregateStack.distribution = .fillProportionally
        pinBackground(otherBackgroundView, to: aggregateStack)

        // If I comment out the following, there are no constraint conflicts
        aggregateStack.spacing = 5

        field1.translatesAutoresizingMaskIntoConstraints = false
        field2.translatesAutoresizingMaskIntoConstraints = false

        aggregateStack.addArrangedSubview(field1)
        aggregateStack.addArrangedSubview(field2)
        rootStack.addArrangedSubview(aggregateStack)

        view.addSubview(rootStack)
        rootStack.translatesAutoresizingMaskIntoConstraints = false

        /**
         * pin the root stackview to the edges of the view controller, just so we can see
         * it's behavior
         */
        NSLayoutConstraint.activate([
            rootStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant:16),
            rootStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant:-16),
            rootStack.topAnchor.constraint(equalTo: view.topAnchor, constant:32),
            rootStack.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant:-32),
            ])
    }

    /**
     * Inserts a UIView into the UIStackView's hierarchy, but not as part of the arranged subviews
     * see https://useyourloaf.com/blog/stack-view-background-color/
     */
    private func pinBackground(_ view: UIView, to stackView: UIStackView) {
        view.translatesAutoresizingMaskIntoConstraints = false
        stackView.insertSubview(view, at: 0)
        NSLayoutConstraint.activate([
            view.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
            view.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
            view.topAnchor.constraint(equalTo: stackView.topAnchor),
            view.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
            ])
    }

    override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

        super.traitCollectionDidChange(previousTraitCollection)

        switch traitCollection.verticalSizeClass {
        case .regular:
            aggregateStack.axis = .vertical
        case .compact:
            aggregateStack.axis = .horizontal
        case .unspecified:
            print("Unexpected")
        }
    }
}

enter image description here

...