23

I try to set up a view with AutoLayout constraints by using constraintEqualToAnchor():

override func viewDidLoad() {
    super.viewDidLoad()

    let myView = UIView()
    myView.backgroundColor = UIColor.orangeColor()
    myView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(myView)

    myView.leftAnchor.constraintEqualToAnchor(view.leftAnchor).active = true
    myView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor).active = true
    myView.topAnchor.constraintEqualToAnchor(view.topAnchor).active = true
    myView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor).active = true

    /******************************************/
    /* I try to change one of the constraints */
    /******************************************/
    myView.leftAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -100).active = true  
}

In the last line of code, I try to change one of the constraints. I thought it would work but it gives some error in the console log

"<NSLayoutConstraint:0x7fb53a5180d0 H:|-(0)-[UIView:0x7fb53a5190b0](LTR)   (Names: '|':UIView:0x7fb53a519240 )>",
"<NSLayoutConstraint:0x7fb53a51f660 H:[UIView:0x7fb53a519240]-(-100)-[UIView:0x7fb53a5190b0](LTR)>",
"<NSLayoutConstraint:0x7fb53a711ee0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7fb53a519240(414)]>"

When using constraintEqualToAnchor()?, what's the right way to change the constraint later after I have set them?

Lucas Derraugh
  • 6,929
  • 3
  • 27
  • 43
Joe Huang
  • 6,296
  • 7
  • 48
  • 81
  • I thought I could set `myView.leftAnchor.constraintEqualToAnchor(view.leftAnchor).active` to `false` first before changing it (the last line), but it still fails. I think I have some misunderstanding here... – Joe Huang Dec 21 '15 at 09:51
  • Have you tried keeping a reference to the constraint and then setting the constant directly? You cannot keep applying new constraints to the view or else it will become over constrained. – Lucas Derraugh Dec 21 '15 at 09:52
  • @LucasDerraugh I am not sure how to do it with layout anchors. Could you show me the code in the answer? – Joe Huang Dec 21 '15 at 09:53
  • let me type one up for you. – Lucas Derraugh Dec 21 '15 at 09:55
  • 1
    @LucasDerraugh yes, I thought I was overwriting an existing constraint but I also found I was actually adding new constraint. However, the problem is that I don't know how to remove/deactivate an existing constraint first with "layout anchor" method. – Joe Huang Dec 21 '15 at 09:57

4 Answers4

41

You need to deactivate the previous constraint when activating a new one so that you don't end up over constraining your view. To do that, store a reference to each of the constraints as a property in your ViewController and then set the active property of the old constraint to false before creating and activating the new constraint:

Swift 2.x:

class ViewController: UIViewController {
    var leftConstraint: NSLayoutConstraint?
    var trailingConstraint: NSLayoutConstraint?
    var topConstraint: NSLayoutConstraint?
    var bottomConstraint: NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()

        let myView = UIView()
        myView.backgroundColor = UIColor.orangeColor()
        myView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(myView)

        leftConstraint = myView.leftAnchor.constraintEqualToAnchor(view.leftAnchor)
        leftConstraint?.active = true

        trailingConstraint = myView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)
        trailingConstraint?.active = true

        topConstraint = myView.topAnchor.constraintEqualToAnchor(view.topAnchor)
        topConstraint?.active = true

        bottomConstraint = myView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
        bottomConstraint?.active = true

        /******************************************/
        /* I try to change one of the constraints */
        /******************************************/
        leftConstraint?.active = false
        leftConstraint = myView.leftAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -100)
        leftConstraint?.active = true
    }
}

Update for Swift 3 syntax:

class ViewController: UIViewController {
    var leftConstraint: NSLayoutConstraint?
    var trailingConstraint: NSLayoutConstraint?
    var topConstraint: NSLayoutConstraint?
    var bottomConstraint: NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()

        let myView = UIView()
        myView.backgroundColor = UIColor.orange
        myView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(myView)

        leftConstraint = myView.leftAnchor.constraint(equalTo: view.leftAnchor)
        leftConstraint?.isActive = true

        trailingConstraint = myView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        trailingConstraint?.isActive = true

        topConstraint = myView.topAnchor.constraint(equalTo: view.topAnchor)
        topConstraint?.isActive = true

        bottomConstraint = myView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        bottomConstraint?.isActive = true

        /******************************************/
        /* I try to change one of the constraints */
        /******************************************/
        leftConstraint?.isActive = false
        leftConstraint = myView.leftAnchor.constraint(equalTo: view.rightAnchor, constant: -100)
        leftConstraint?.isActive = true
    }
}
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • Awesome, thanks! I am wondering why I need a variable to store a constraint, why can't I change `.active=false` directly (i.e. myView.leftAnchor.constraintEqualToAnchor(view.leftAnchor).active = false)? – Joe Huang Dec 21 '15 at 12:59
  • 4
    `myView.leftAnchor.constraintEqualToAnchor(view.leftAnchor)` creates a new constraint. It doesn't access the old one. – vacawama Dec 21 '15 at 13:01
  • I see, thanks! And is it possible to animate this kind of constraint change? I am actually trying to achieve a page sliding-in effect by changing the constraints. I just tried with `UIView.animation()`, there is no animation unless I use "constant" (but I hope I don't need to use constant). – Joe Huang Dec 21 '15 at 13:03
  • 1
    I believe you should be able to animate that change. Call `layoutIfNeeded()` inside of the loop: `UIView.animateWithDuration(1.0) { self.view.layoutIfNeeded() }` – vacawama Dec 21 '15 at 13:11
  • Yes, you are right. It animates, thanks! But it's interesting for a case that it doesn't. If I set the top/width/height constraint to be equal to the parent's top/width/height, and left anchor to be aligned with the parent's right anchor and try to set the left anchor to be the parent's left anchor. In this case (sliding in effect), it doesn't animate. If I set the left anchor to be the parent's left anchor and then try to animate to the parent's right anchor, in this case (sliding out), it animates. – Joe Huang Dec 21 '15 at 13:27
  • In `animationWithDuration`, I actually want to call `myView.layoutIfNeeded()` because if I call it on the parent view, all other subviews will be animated, isn't it? And if I call it on `myView`, the case I mentioned in my last comment (sliding in effect) does not animate. – Joe Huang Dec 21 '15 at 23:53
  • to change constant just use `leftConstraint.constant = newValue ` – Husam Aug 07 '17 at 19:33
  • @Husam, OP is doing more than just changing the `constant`. The original left constraint was based on `view.leftAnchor` and new left constraint is based on `view.rightAnchor`. – vacawama Aug 07 '17 at 20:14
  • @vacawama yes yes I know that, I just added a hint if someone needed to change constant ... sorry for inconvenience – Husam Aug 07 '17 at 20:23
3

Here is an example declaring a constraint c which will be referenced later in time. We set a new constant value and then call layout on the superview.

myView.translatesAutoresizingMaskIntoConstraints = false

var constraints: [NSLayoutConstraint] = [
    myView.topAnchor.constraintEqualToAnchor(view.topAnchor),
    myView.leftAnchor.constraintEqualToAnchor(view.leftAnchor),
    myView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
]         
let c = myView.rightAnchor.constraintEqualToAnchor(view.rightAnchor)       
constraints.append(c)   

view.addSubview(myView)
NSLayoutConstraint.activateConstraints(constraints)

// Some time later
c.constant = -100
view.setNeedsLayout()
Lucas Derraugh
  • 6,929
  • 3
  • 27
  • 43
  • Assigning a new constant cannot solve my problem though. I need to remove a constraint and add a new one. Is it possible to remove/deactivate a constraint? – Joe Huang Dec 21 '15 at 12:15
  • You can see my last line is not just to change the constant value but a new constraint. The left anchor was aligned with the parent's left anchor and I want to change it to be aligned with the parent's right anchor minus 100. It's not just a constant change. – Joe Huang Dec 21 '15 at 12:41
  • I have found this kind of constant change online however I just can't find how to remove/deactivate a constraint so that I can add a new constraint programatically at run time. – Joe Huang Dec 21 '15 at 12:42
  • @Joe You should not create new constraints if all you're doing is changing the constant. There is no reason you cannot accomplish what you stated in your question with this approach. – Lucas Derraugh Dec 22 '15 at 01:23
  • considering orientation to be handled automatically, it's kind of hard to use constant for my case. How do you use constant to place a view off screen (frame.width + 1 exactly) by using constant when considering the user might change orientation at any given time – Joe Huang Dec 22 '15 at 01:44
  • @JoeHuang Well then you can do the same thing, simply disabling the constraint you don't want and enabling the one you want active. Anyways, looks like you found the solution you needed. – Lucas Derraugh Dec 22 '15 at 03:51
1
// method myView.topAnchor.constraintEqualToAnchor creates new one inactive anchor 
// and not returns exist with equal relationships     

// you can set identifier for any constraint in Interface Builder or code and find & update in code
for ( NSLayoutConstraint *c in [self.view constraintsAffectingLayoutForAxis:UILayoutConstraintAxisHorizontal] )
{
    if ( YES == [c.identifier isEqualToString:@"my.layout-constraint.id"] )
    {
        // Unlike the other properties, the constant can be modified
        // after constraint creation. 
        // Setting the constant on an existing constraint performs much better 
        // than removing the constraint and adding a new one that's exactly like 
        // the old except that it has a different constant.

        c.constant = 123;

        // if needed
        // [self.view setNeedsUpdateConstraints];

        break;
    }
}
Roman Solodyashkin
  • 799
  • 12
  • 17
0

Here is example with if statement modifying a StackView and View when a segmented clicked:

if (sender as AnyObject).selectedSegmentIndex == 0{
    // Shrink the white view and stack view
    heightConstraintView = containerView.heightAnchor.constraint(equalToConstant: 100)
    heightConstraintView?.isActive = true
    heightConstraintStackView = stackView.heightAnchor.constraint(equalToConstant: 100)
    heightConstraintStackView?.isActive = true
} else {
    // Before returning back the white view and stack view DEACTIVATE teh previous constraints
    heightConstraintView?.isActive = false
    heightConstraintStackView?.isActive = false
    // Returning back the white view and stack view to normal size
    heightConstraintView = containerView.heightAnchor.constraint(equalToConstant: 200)
    heightConstraintView?.isActive = true
    heightConstraintStackView = stackView.heightAnchor.constraint(equalToConstant: 200)
    heightConstraintStackView?.isActive = true
}
Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
Ahmadiah
  • 476
  • 5
  • 12