19

When programmatically creating layouts, I follow Apple's advice: override -updateConstraints, add custom constraints, and call -setNeedsUpdateConstraints once subviews have been added to the view. My typical setup looks like this:

- (void)setupViews
{
    //Style View

    //Add gesture recognizers

    //Add Subviews


    [self setNeedsUpdateConstraints];
}

- (void)updateConstraints
{
    //Add custom constraints       

    [super updateConstraints];
}

The problem

There are occasions when -updateConstraints gets fired multiple times (for example, when a view's controller is presented or pushed w/ animation). The problem here is that each constraint added gets re-added. This becomes a serious problem when trying to on-demand change the constant of an added constraint, since there are two of the original constraint that subsequently conflict with each other. I imagine that even when you aren't manipulating the constraints after creating them, having double what you doesn't seem good.

Potential solutions

1 - Remove all constraints effecting the view before applying them in -updateConstraints:

 - (void)updateConstraints
    {   
        //Remove all constraints affecting view & subviews

        //Add custom constraints

        [super updateConstraints];       
    }

2 - Set a layout flag & check against it before adding custom constraints:

- (void)updateConstraints
{
    if (self.didAddConstraints) {
        [super updateConstraints];
        return;
    }

    //Add custom constraints   

    self.didAddConstraints = YES;    

    [super updateConstraints];
}

3 - Don't worry about doubling up on constraints, and whenever changing a constant is needed, only remove that constraint before re-adding.

3 - Something awesome that I haven't thought of.


What's the best practice here?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Whoa
  • 1,334
  • 11
  • 26
  • 1
    I think you might be misusing `setNeedsUpdateConstraints` and `updateConstraints`. You mentioned updating constants - in that case, you only need `setNeedsLayout`. My understanding is that `updateConstraints` is intended for wholesale changes to the layout - if you are changing the structure of the view, not just parameters. It is intended to have one place / time to dramatically change constraints, to avoid churn of updating after small state changes. That said, in the state property setting code, you also update the specific corresponding constraint, and call setNeedsDisplay. – Chris Conover May 21 '15 at 22:22

2 Answers2

17

Short answer: Potential solution number 2.

Removing and reapplying all constraints may become costly as the layout becomes more complex. Besides, if your layout is stateful, you'd have more problems.

Doubling constraints is very inefficient, you can never know how many times updateConstraints might be called.

As this blog post shows, using a flag is the simplest, most efficient way of dealing with this problem. That is how I deal with it myself.

As a side note, you mention of there existing an awesome way that you have not thought of yet. Most of the times, the simplest way IS the most awesome way. :)

Akhrameev
  • 325
  • 4
  • 12
duci9y
  • 4,128
  • 3
  • 26
  • 42
  • 3
    I agree, and this is generally what Apple UIKit engineers recommend. The most important point is to avoid removing constraints once the auto layout engine has done a layout pass -- it's unbelievably expensive to remove a bunch of constraints, re-add some more, and then re-calculate the layout. If you do want to make changes to constraints, adjust the `constant` property on existing ones because it is very efficient for the auto layout engine to re-solve. – smileyborg Jul 07 '14 at 15:41
  • Also, just want to note that (as Apple's documentation for these methods says) **you must call `super` at the very end of your implementation**. If you call it before making changes to some constraints, you may hit a runtime exception (crash). Usually this isn't an issue, but it can happen for specific constraints (e.g. a height constraint on the view controller's view whose `-updateViewConstraints` method is being called). – smileyborg Mar 10 '15 at 17:12
  • I am wondering what is wrong with adding the constraints in an `init` (includes `awakeFromNib`) method? I am really uncomfortable with an extra flag var. – CopperCash Apr 19 '15 at 03:15
  • @CopperCash Unfortunately the layout engine is just not ready at the point where views are loaded from nibs. The view needs to be a part of the view hierarchy too for constraints to be applied. Try it out for yourself and learn from the errors you get. – duci9y Apr 19 '15 at 04:21
  • @duci9y I have used AutoLayout in the `init` (includes `awakeFromNib`) methods of my `UIView`s for hundreds of times. If there was something wrong, I am sure I would know it long before. I just want to know if there are some benefits to setup constraints in `updateViewConstraints` rather than `init`. – CopperCash Apr 21 '15 at 15:03
  • @CopperCash I wouldn't worry about it, I use the init method to set up basically EVERYTHING for subclassed uiviews and this includes the settgin of auto layout constraints and the fun part about doing it this way is you never have to deal with all the BS you see above, you can even set property constraints in the UIView subview in order to control the constraint constants and you will really never suffer the problem of needing to call to "super updateConstraints" or anything of the sort. Doing all of this in the "init" solves nearly every problem people have with autolayout – Larry Pickles Aug 19 '15 at 19:48
0

This kind of tracking could be done too, for the initial setup. In most cases.

override func updateConstraints() {
            if constraints.count == 0 {
                let views = ["textField": textField]
                addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[textField]-0-|", options: [], metrics: nil, views: views))
                addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[textField]-0-|", options: [], metrics: nil, views: views))
            }
            super.updateConstraints()
        }