Where should I be setting autolayout constraints when creating views programmatically
Asked Answered
O

9

81

I see different examples where constraints are set. Some set them in viewDidLoad / loadView (after the subview was added). Others set them in the method updateViewConstraints, which gets called by viewDidAppear.

When I try setting constraints in updateViewContraints there can be a jumpiness to the layout, e.g. slight delay before the view appears. Also, if I use this method, should I clear out existing constraints first i.e. [self.view [removeConstraints:self.view.constraints]?

Orpheus answered 15/10, 2013 at 18:3 Comment(2)
I've had the same experience with updateViewConstraints, so I stopped trying to use it. I configure constraints in viewDidLoad, or in a custom view's updateConstraints method. Hopefully, someone will give you a definitive answer.Telephone
updateViewConstraints: You may override this method in a subclass in order to add constraints to the view or its subviews. (from the Apple docs)Pelagian
H
110

I set up my constraints in viewDidLoad/loadView (I'm targeting iOS >= 6). updateViewConstraints is useful for changing values of constraints, e.g. if some constraint is dependent on the orientation of the screen (I know, it's a bad practice) you can change its constant in this method.

Adding constraints in viewDidLoad is showed during the session "Introduction to Auto Layout for iOS and OS X" (WWDC 2012), starting from 39:22. I think it's one of those things that are said during lectures but don't land in the documentation.

UPDATE: I've noticed the mention of setting up constraints in Resource Management in View Controllers:

If you prefer to create views programmatically, instead of using a storyboard, you do so by overriding your view controller’s loadView method. Your implementation of this method should do the following:

(...)

3.If you are using auto layout, assign sufficient constraints to each of the views you just created to control the position and size of your views. Otherwise, implement the viewWillLayoutSubviews and viewDidLayoutSubviews methods to adjust the frames of the subviews in the view hierarchy. See “Resizing the View Controller’s Views.”

UPDATE 2: During WWDC 2015 Apple gave a new explanation of updateConstraints and updateViewConstraints recommended usage:

Really, all this is is a way for views to have a chance to make changes to constraints just in time for the next layout pass, but it's often not actually needed.

All of your initial constraint setup should ideally happen inside Interface Builder.

Or if you really find that you need to allocate your constraints programmatically, some place like viewDidLoad is much better.

Update constraints is really just for work that needs to be repeated periodically.

Also, it's pretty straightforward to just change constraints when you find the need to do that; whereas, if you take that logic apart from the other code that's related to it and you move it into a separate method that gets executed at a later time, your code becomes a lot harder to follow, so it will be harder for you to maintain, it will be a lot harder for other people to understand.

So when would you need to use update constraints?

Well, it boils down to performance.

If you find that just changing your constraints in place is too slow, then update constraints might be able to help you out.

It turns out that changing a constraint inside update constraints is actually faster than changing a constraint at other times.

The reason for that is because the engine is able to treat all the constraint changes that happen in this pass as a batch.

Haddix answered 15/10, 2013 at 19:12 Comment(8)
+1 for this as the best answer. Apple prescribes loadView as the correct place to set initial constraints and this doesn't require an extra BOOL flag in the updateConstraints method (which just seems hacky).Elvera
I my opinion the view should be responsible for the constraints, not the view controller. In a lot of cases the view controller even doesn't know what all the elements in the view are (for example static labels in a table view cell).Intricacy
@Intricacy How can the view control its relationship to other views? You have to set constraints like @"|-[button1]-[button2]-|" in the ViewController, right? Or is there a different way?Orchidectomy
@Casper Most of the times I have a UIView subclass, which is the view of the view controller. Within that view there are subviews and the constraints for the subviews.Intricacy
Why is it a bad practice to change constraints in updateViewConstraints?Pelagian
While it is fine to create and activate constraints any time after your view hierarchy is set up, I encourage doing it in -updateConstraints or -updateViewConstraints as this answer recommends. Why? Aside from the fact that Apple's docs say so, these methods are guaranteed to be called at the correct time in the layout cycle. Also important: it's highly beneficial to know all of your layout code & constraints are created and modified in the same place, with a single entry point. If you do it in an idempotent fashion, it's easy to reason about.Reichard
I really want to put my all constraints-setup code in one place. But I hate flags. Otherwise the method name is -updateConstraints, not -createConstraints.Solidary
What is the proper way to update constraints? say i have a root controller with 2 different controllers it loads one is the log in, but once that controller is finished what it has to do now i load in the next controller. So my first idea is to nullify the log in VC and check if its alive when updatingthe consttraint. If its not alive then i know the other VC should be displayed so i update the constraints based upon the VC that is being displayed. Is this correct?Petersburg
P
32

I recommend creating a BOOL and setting them in the -updateConstraints of UIView (or -updateViewConstraints, for UIViewController).

-[UIView updateConstraints]: (apple docs)

Custom views that set up constraints themselves should do so by overriding this method.

Both -updateConstraints and -updateViewConstraints may be called multiple times during a view's lifetime. (Calling setNeedsUpdateConstraints on a view will trigger this to happen, for example.) As a result, you need to make sure to prevent creating and activating duplicate constraints -- either using a BOOL to only perform certain constraint setup only once, or by making sure to deactivate/remove existing constraints before creating & activating new ones.

For example:

  - (void)updateConstraints {  // for view controllers, use -updateViewConstraints

         if (!_hasLoadedConstraints) {
              _hasLoadedConstraints = YES;
             // create your constraints
         }
         [super updateConstraints];
    }

Cheers to @fresidue in the comments for pointing out that Apple's docs recommend calling super as the last step. If you call super before making changes to some constraints, you may hit a runtime exception (crash).

Plaything answered 16/10, 2013 at 8:31 Comment(10)
I'm not sure if it makes any difference pragmatically, but the documentation says 'Important: Call [super updateConstraints] as the final step in your implementation.'Klatt
According to the docs if you change a view at runtime and invalidate your constraints then the system immediately removes that constraint and calls setNeedsUpdateConstraints. Before a new layout is performed the system calls updateConstraints where you can customize the invalidated layout. As such, I probably wouldn't be setting a flag on this method which might prevent the system from calling it.Cordelier
@cocoanutmobile If you use a BOOL flag as suggested in this answer, it's just to prevent some of your constraints from being added more than once. It's a perfectly fine thing to do. Another alternative is to simply store a reference to any constraints you create, and then make sure to remove (deactivate) all of those before you create and activate any new ones. This approach will yield worse performance however, especially if your old and new constraints are the exact same.Reichard
@cocoanutmobile Also, note that the BOOL flag here doesn't prevent the system from calling -updateConstraints or anything -- it would still get called, and you still call [super updateConstraints].Reichard
@Reichard Do you have a code example where updateConstraints is being called multiple times and adding the same constraints multiple times. I haven't encountered this. I'm assuming the point of setting the flag is to prevent updateConstraints from being called once the constraints have been loaded and to simply remove and add a subset of the constraints. I haven't encountered this issue of updateConstraints being called repeatedly. Does anyone have a code sample? Why not just call updateViewConstraints from viewWillAppear and then again if you rotate. Then decide when whether to call...Cordelier
setNeedsUpdateConstraints. It's up to you whether to call setNeedsUpdateConstraints or just remove a few constraints and add a few in in a method instead. I fail to see a good reason to set a flag personally.Cordelier
@cocoanutmobile There are definitely cases where -updateConstraints is called multiple times, typically because of some change you make to a view, which causes UIKit to internally set the "needs update constraints" flag. Try presenting/dismissing, pushing/popping view controllers -- you may see that trigger more than one call. I've also seen setting a UIButton's title/title color trigger it. Another thing you can do is look at UIView.h runtime header from class-dump, and search for "updateConstraints"...you'll see some hits like _needsDoubleUpdateConstraintsPass.Reichard
@cocoanutmobile As far as calling -updateConstraints or -updateViewConstraints directly yourself, you shouldn't do that, just like you shouldn't call -layoutSubviews yourself (see this excerpt from Apple's docs on that API). Instead, you should call -setNeedsUpdateConstraints and let UIKit call those methods. (It will coalesce multiple calls, for example.) If you really need to do a synchronous update, you can call -updateConstraintsIfNeeded on the view, which tells UIKit to immediately call -updateConstraints assuming that it needs an update.Reichard
As @Klatt mentioned, make sure to call super at the very end of your implementation of -updateConstraints or -updateViewConstraints. See this comment for more info.Reichard
Why not Apple build the BOOL flag in UIKit and pass it out?Troat
C
5

This should be done in ViewDidLoad, as per WWDC video from Apple and the documentation.

No idea why people recommend updateConstraints. If you do in updateConstraints you will hit issues with NSAutoresizingMaskLayoutConstraint with auto resizing because your views have already taken into account the auto masks. You would need to remove them in updateConstraints to make work.

UpdateConstraints should be for just that, when you need to 'update' them, make changes etc from your initial setup.

Craiova answered 17/3, 2018 at 15:3 Comment(1)
Can you add the links for the video and documentation you are referring here.Staffard
C
3

Do it in view did layout subviews method

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
}
Christadelphian answered 25/7, 2016 at 7:48 Comment(1)
Why? That sounds too late. Could be wrong.Hitch
F
1

I have this solution to change constraints before those who are in the storyboard are loaded. This solution removes any lags after the view is loaded.

-(void)updateViewConstraints{

    dispatch_async(dispatch_get_main_queue(), ^{

            //Modify here your Constraint -> Activate the new constraint and deactivate the old one

            self.yourContraintA.active = true;
            self.yourContraintB.active= false;
            //ecc..
           });

    [super updateViewConstraints]; // This must be the last thing that you do here -> if! ->Crash!
}
Foal answered 10/3, 2016 at 16:40 Comment(0)
A
1

You can set them in viewWillLayoutSubviews: too:

 override func viewWillLayoutSubviews() {

    if(!wasViewLoaded){
        wasViewLoaded = true

        //update constraint

        //also maybe add a subview            
    }
}
Acre answered 20/7, 2016 at 5:14 Comment(0)
M
0

This worked for me:

Swift 4.2

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

// Modify your constraints in here

  ...

}

Although honestly I am not sure if it is worth it. It seems a bit slower to load than in viewDidLoad(). I just wanted to move them out of the latter, because it's getting massive.

Managing answered 14/8, 2018 at 17:41 Comment(0)
T
0

Following example is to pass any view to another class. create my view from storyboard

Swift 5.0

    override func viewWillAppear(_ animated: Bool) {
        
      super.viewWillAppear(animated) 
        DispatchQueue.main.async {
            self.abcInstance = ABC(frame: self.myView.frame)
          } 
      }

 

If you miss DispatchQueue.main.async, it will take time to update constraints in viewWillAppear. Create myView in storyboard and give constraints same as screen width & height, then try printing frame of myView. It will give accurate value in DispatchQueue.main.async or in viewDidAppear but not give accurate value in viewWillAppear without DispatchQueue.main.async.

Theresiatheresina answered 4/8, 2020 at 9:40 Comment(0)
H
0

Add your constraints in viewWillLayoutSubviews() to add constraints programmatically

See Apple Documentation in Custom Layout Section

If possible, use constraints to define all of your layouts. The resulting layouts are more robust and easier to debug. You should only override the viewWillLayoutSubviews or layoutSubviews methods when you need to create a layout that cannot be expressed with constraints alone.

Hygroscopic answered 29/4, 2021 at 21:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.