Home>

I am studying Auto Layout.
I wrote the following code and wrote a program that view2 moves 100 away from view1 when the button is pressed, but it does not leave 100 between views even when the button is pressed.
Looking at the log, it seems that the constraint set at the first time (attaching the top of view2 and the bottom of view1) is selected and used on the program side.
I would appreciate it if you could teach me how to change the layout as intended.

What I tried myself

I tried disabling the constraint that attaches view2 and view1, but the result did not change

import UIKit
class ViewController: UIViewController {
    let view1 = CustomView1 ()
    let view2 = CustomView2 ()
    let view3 = CustomView3 ()
    override func viewDidLoad () {
        super.viewDidLoad ()
        setupView1 ()
        setupView2 ()
        setupView3 ()
    }
    // When this method is executed, the constraints of view2 will be changed.
    @IBAction func changeConstraint (_ sender: Any) {
        changeconstraintsView2 ()
    }
    func setupView1 () {
        view1.backgroundColor = .red
        view.addSubview (view1)
        view1.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate ([[
            view1.heightAnchor.constraint (equalToConstant: 100),
            view1.widthAnchor.constraint (equalToConstant: view.bounds.width),
            view1.topAnchor.constraint (equalTo: view.safeAreaLayoutGuide.topAnchor),
            view1.leftAnchor.constraint (equalTo: view.safeAreaLayoutGuide.leftAnchor),
            view1.rightAnchor.constraint (equalTo: view.safeAreaLayoutGuide.rightAnchor)
        ])
    }

    func setupView2 () {
        view2.backgroundColor = .blue
        view.addSubview (view2)
        view2.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate ([[
            view2.heightAnchor.constraint (equalToConstant: 100),
            view2.widthAnchor.constraint (equalToConstant: view.bounds.width),
            view2.topAnchor.constraint (equalTo: view1.bottomAnchor),
            view2.leftAnchor.constraint (equalTo: view.safeAreaLayoutGuide.leftAnchor),
            view2.rightAnchor.constraint (equalTo: view.safeAreaLayoutGuide.rightAnchor)
        ])
    }
    func setupView3 () {
        view3.backgroundColor = .black
        view.addSubview (view3)
        view3.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate ([[
            view3.heightAnchor.constraint (equalToConstant: 100),
            view3.widthAnchor.constraint (equalToConstant: view.bounds.width),
            view3.topAnchor.constraint (equalTo: view2.bottomAnchor),
            view3.leftAnchor.constraint (equalTo: view.safeAreaLayoutGuide.leftAnchor),
            view3.rightAnchor.constraint (equalTo: view.safeAreaLayoutGuide.rightAnchor)
        ])
    }
    // change view2 constraints
    func changeconstraintsView2 () {
        view2.topAnchor.constraint (equalTo: view1.bottomAnchor) .isActive = false
        NSLayoutConstraint.activate ([[
            view2.heightAnchor.constraint (equalToConstant: 100),
            view2.widthAnchor.constraint (equalToConstant: view.bounds.width),
            view2.topAnchor.constraint (equalTo: view1.bottomAnchor, constant: 100),
            view2.leftAnchor.constraint (equalTo: view.safeAreaLayoutGuide.leftAnchor),
            view2.rightAnchor.constraint (equalTo: view.safeAreaLayoutGuide.rightAnchor)
        ])
        view2.layoutIfNeeded ()
    }

}
class CustomView1: UIView {
    override func updateConstraints () {
        super.updateConstraints ()
        print ("customview1's constraints are updated")
    }
}
class CustomView2: UIView {
    override func updateConstraints () {
        super.updateConstraints ()
        print ("customview2's constraints are updated")
    }
}
class CustomView3: UIView {
    override func updateConstraints () {
        super.updateConstraints ()
        print ("customview3's constraints are updated")
    }
}
  • Answer # 1

    With the current code, when you press a button

    2020-11-13 10: 11: 18.917562 + 0900 303908_AutoLayout [52953: 1061850] [LayoutConstraints] Unable to simultaneously satisfy constraints.
        Probably at least one of the constraints in the following list is one you don't want.
        Try this:
            (1) look at each constraint and try to figure out which you don't expect;
            (2) find the code that added the unwanted constraint or constraints and fix it.
    (
        "<NSLayoutConstraint: 0x6000008d2800 V: [_03908_AutoLayout.CustomView1: 0x7ff126905be0]-(0)-[_03908_AutoLayout.CustomView2: 0x7ff126906380] (active)>",
        "<NSLayoutConstraint: 0x6000008d9b30 V: [_03908_AutoLayout.CustomView1: 0x7ff126905be0]-(100)-[_03908_AutoLayout.CustomView2: 0x7ff126906380] (active)>"
    )
    Will attempt to recover by breaking constraint<NSLayoutConstraint: 0x6000008d9b30 V: [_03908_AutoLayout.CustomView1: 0x7ff126905be0]-(100)-[_03908_AutoLayout.CustomView2: 0x7ff126906380] (active)>Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
    The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in<UIKitCore/UIView.h>may also be helpful.

    I think there is a warning.

    This is a warning due to the different constraints placed on view2.
    AutoLayout only adopts the conflicting constraints that seem most appropriate at the time.

    That's a contradiction,

      func changeconstraintsView2 () {
            view2.topAnchor.constraint (equalTo: view1.bottomAnchor) .isActive = falseNSLayoutConstraint.activate ([[
                view2.heightAnchor.constraint (equalToConstant: 100),
                view2.widthAnchor.constraint (equalToConstant: view.bounds.width),
                view2.topAnchor.constraint (equalTo: view1.bottomAnchor, constant: 100),
                view2.leftAnchor.constraint (equalTo: view.safeAreaLayoutGuide.leftAnchor),
                view2.rightAnchor.constraint (equalTo: view.safeAreaLayoutGuide.rightAnchor)
            ])
            view2.layoutIfNeeded ()
        }

    I'm going to change the constraints here, but in fact"Add new constraints"Because it has been done.

    The reason is that the constant between view1 and view2 is currently 0, but it is inconsistent because I tried to add a new 100 constraint.

    Therefore, as in the console output

    Will attempt to recover by breaking constraint<NSLayoutConstraint: 0x6000008d9b30 V: [_03908_AutoLayout.CustomView1: 0x7ff126905be0]-(100)-[_03908_AutoLayout.CustomView2: 0x7ff126906380] (active)>

    Discard the constant 100 constraint set between view1 and view2 (Will attempt to recover by breaking constraint) To solve the problem.

    Then, in the case of dynamic change, I think that the flow is to record the value when the constraint is applied for the first time and change the constant term as needed.
    With StoryBoard, it's like creating an Outlet for a constraint.

    First, prepare a variable that stores the constraint value as a property of the class.

      var view2TopConstraint: NSLayoutConstraint?

    Then, when you first set the constraint, you apply the constraint while preserving the value of the constraint, which may change the constraint later.

          view2TopConstraint = view2.topAnchor.constraint (equalTo: view1.bottomAnchor)
            NSLayoutConstraint.activate ([[
                view2.heightAnchor.constraint (equalToConstant: 100),
                view2.widthAnchor.constraint (equalToConstant: view.bounds.width),
            // view2.topAnchor.constraint (equalTo: view1.bottomAnchor),
                view2TopConstraint !,
                view2.leftAnchor.constraint (equalTo: view.safeAreaLayoutGuide.leftAnchor),
                view2.rightAnchor.constraint (equalTo: view.safeAreaLayoutGuide.rightAnchor)
            ])

    When you change the constraint, do something like this:
    Because it is a change of the constant termconstantTo change.

      func changeconstraintsView2 () {
            // add 100
            view2TopConstraint? .Constant + = 100
            view2.layoutIfNeeded ()
        }

    In this case, a 100px gap is added between view1 and view2 each time the button is pressed.

    This should work as expected.