Why using NSLayoutConstraint.activate is more efficient than .isActive = true? - ios

My question is about the creation of constraints: why creating an array of constraints to be activated later with the activate method is more efficient than activating each one by one with .isActive = true?
Activating a direct constraint, we're basically changing an attribute, a variable that's already instantiated in the UIView we're tinkering with:
#available(iOS 9.0, *)
open var topAnchor: NSLayoutYAxisAnchor { get }
But the definition shows that using the Array is more efficient:
/* Convenience method that activates each constraint in the contained array, in the same manner as setting active=YES.
This is often more efficient than activating each constraint individually. */
#available(iOS 8.0, *)
open class func activate(_ constraints: [NSLayoutConstraint])
I don't see how to create an array of a variable that is already in scope and then pass it in a loop to activate it. How is this more efficient than enabling direct constraint?

When you have multiple constraints you need to activate, AutoLayout has no idea when you're going to be "done" activating them if you go one by one. This might lead it to perform some intermediate calculations between constraints, whose results are wasted by adding another constraint immediately afterwards. By providing AutoLayout a list of constraints at once, it can both make sure it doesn't perform any intermediate calculations in between activations, and can get a better idea of the changes being made, leading to potentially more accurate and performant calculations.
If you poke around in /System/Library/PrivateFrameworks/CoreAutoLayout.framework with a disassembler like Hopper, you might (currently) see:
The implementation of +[NSAutolayoutConstraint activateConstraints:] is a passthrough to the private +[NSAutolayoutConstraint _addOrRemoveConstraints:activate:]
+[NSAutolayoutConstraint _addOrRemoveConstraints:activate:], for each constraint, tries to get a reference to the layout engine that the constraint belongs to (-[NSLayoutConstraint _layoutEngine]), and
If it belongs to an engine, activates or deactivates the constraint by calling through to the engine's -[NSISEngine withAutomaticOptimizationsDisabled:] method
If it doesn't yet belong to an engine, it will activate the constraint directly
-[NSLayoutConstraint setActive:] itself also does a non-trivial amount of work, potentially searching for its _nearestAncestorLayoutItem to _findCommonAncestorOfItem:andItem: in order to insert into the right place. By doing this work inside of a context that has optimizations disabled, autolayout can avoid doing that unnecessary work for each and every one of the constraints in the list.
The amount of work that AutoLayout has to do for each of your constraints likely dwarfs the cost of creating an array to store them temporarily, though you can likely profile this in a live app to see what it might be costing you.

Related

Where to put programatic constraints for MVC

I want to practice creating simple apps using no storyboard. I am able to do the constraints programmatically (slowly) but I want to also practice separating my code into MVC. Is there a particular place/method that I am supposed to write the programatic constraints? Or does it not matter?
Good discussion in the comments. My thoughts, based on that discussion?
With an understanding that the question is subjective, you place your constraints:
The earliest in a view controller's life cycle where they work.
As "close" to the view as possible.
If it's something common, make it as universal as possible.
Understand how your specifics fit into everything.
(Understand, the question isn't limited to constraints. It could apply to hierarchies, UI, even database tables when you get down to it!)
Sticking to constraints, and my answer....
(1) Use the UIViewController and UIView lifecycles.
Generally the view life cycle is loadView, viewDidLoad, viewWillAppear, viewWillLayoutSubviews, viewDidLayoutSubviews, and viewDidAppear. great SO answer detailing this.
I believe that loadView is too early for constraints, but not viewDidLoad - **provided you aren't expecting to know the frame size. While many say viewDidLayoutSubviews is the right place for that, I've found that viewWillLayoutSubviews most times works just as well. Either way, get your constraints set as soon as possible!
(2) Do it as close to the view as possible.
If you have subviews - I have a "ToolBar" class of objects - you want the constraints, at least as much as possible, to be coded inside the class. For instance, in my tool bar, it slides out, has buttons, and even rotates upon orientation. The only constraints not inside these classes is for orientation - that owner is (and I believe should be) the view controller instantiating it.
(3) Make it universal.
I plan to use this tool bar across a few apps. So the first thing I did was add it to a framework. This framework was needed because I had an app that I delivered a photo editing exension - and the "edit" screen is as much the same as possible. In the end I move all my constraints there. (At least as much as possible.) Anything that I believe is reusable.
(4) Understand the specific requirements of your app.
This should be obvious. If you need to code for various orientations, use arrays and activate/deactivate them. (YES, a common mistake is replacing them! That's setting yourself up for some major headaches.)
If you can keep things active, declare the constraint, set `isActive = true1, and forget about it. If you need to adjust that constraint's constant or multiplier, in the declaration name it and then where you need to alter it, do it.
My conclusion? Auto layout is a very useful tool - more so in code. But the placement of code is like asking "how does one code an OOP app for auto rentals" or " how does one design a database for auto rentals". It not just an art, there are many answers. These are the "rules" I try to follow - YMMV.
To get started with this style of development I recommend checking out Let's Build That App as he goes through very in-depth examples of setting up complex apps entirely in code, without storyboards.
The way he structures the constraints is using a custom implementation of UIView, that way your view code is separated from the ViewController. Then, in the viewDidLoad method you can instantiate your implementation of UIView with something like self.view = MyView().
I wrote a few apps like this. The major drawbacks are that it can become very difficult to make quick adjustments, and you really need to learn about all the different types of constraints you can use.
Here's a pastebin of some extensions I used when doing this. I hope this helps.

When and why is updateConstraints called?

When and why is updateConstraints called? And when overriding, what kind work is meant to be done in the override?
I've read and watched lots of information on how Auto Layout works, but i can't seem wrap my head around it completely.
Do you update the constant of any special interest constraints you saved references to, or to do customising do you remove all constraints from your view and recreate them entirely like one guy did in a WWDC video?
Some thoughts about the matter are to be found here:
https://oleb.net/blog/2015/08/how-to-use-updateconstraints/
And of course there is occasional information on this topic in WWDC videos.
But on the whole the answer is probably: don't override updateConstraints. It isn't called very often under normal circumstances, so you'd only need to override it if you were also calling setNeedsUpdateConstraints, and the result is probably an unnecessarily confusing and elaborate architecture. There is generally no need for an updateConstraints override. The WWDC 2018 video on auto layout shows that such overrides are often written incorrectly and just cause a bunch of highly inefficient layout "churn". If you need to change constraints, just change them at the point where the need for the change arises.
As the documentation now says:
It is almost always cleaner and easier to update a constraint immediately after the affecting change has occurred. For example, if you want to change a constraint in response to a button tap, make that change directly in the button’s action method.
You should only override this method when changing constraints in place is too slow, or when a view is producing a number of redundant changes.
That last sentence refers to a situation that is extraordinarily rare, and in any case you're unlikely to know what to do to implement updateConstraints properly to gain efficiency. So don't.

Autolayout constraints update with size classes

I know, how to create autolayout constraints with size classes perfectly.
But I am not getting when to call layOutIfNeeded(), setNeedsDisplay(), layOutSubViews(), setUpdateConstraints().
Can someone tell how to properly call this function to update UI after constraints changed.
Another my concern is, when to call only single function out of above and call with other functions.
It must be really clear that your layout is calculated by a routine that is called at specific times at runtime.
It could happen that you need to modify the current layout, for instance changing the constant of a specific constraint. If you just do that you will notice no changes in the UI, this is because the routine is still not called.
What you can do is force the layout routine to be called, and you do that by these two methods:
setNeedsLayout : You are telling that the view needs a layout. The next time the routine is called knows that this view need to have a layout refresh
layOutIfNeeded(): You don't want to wait the next call and you are telling the system to force layout calculation ASAP
Same thing happen with setNeedsDisplay() and displayIfNeeded(), with the first you tell that a view needs to be rendered again, and with the second you tell do ASAP.
If you are asking yourself why, the reason is performance. Is useless to re-render everything each time, this lazy approach will save system resources.
The methods - setNeedsUpdateConstraints and -updateConstraintsIfNeeded are basically the same concept applied to constraints, the difference is that you will not see any changes in UI until you force a layout, why this methods are useful? because sometimes you need to check after a change in constraint if the layout is still valid without changing the aspect of your UI.

Correct way to delete a NSLayoutConstraint

In my apps I'm working a lot with constraints, in most cases with animations as well. In certain circumstances I need to remove constraints and add new ones.
As I need to support iOS 7 as well, I'm not able to use the active property, which would else be the solution for me.
The way to remove constraints is to use the removeConstraint method on a UIView.
Is it possible to create a method like
constraint.remove()
so you don't have to know which view is taking care over the constraint?
What I do is create arrays of the constraints that I wish to be able to add/remove and simply use the following:
#property NSMutableArray *newConstraints;
Fill up the newConstraints
iOS7 and iOS8:
[self.viewToChange addConstraints:self.newConstraints];
[self.viewToChange removeConstraints:self.newConstraints];
or iOS8 only, use the new mechanism
[NSLayoutConstraint activateConstraints:self.newConstraints];
[NSLayoutConstraint deactivateConstraints:self.newConstraints];
With this you can apply a set, remove the set and apply a new set.
You can also create the initial list from the storyboard set if you can identify which constraints are which.
I've ended up using the method autoRemove provided by the PureLayout library: https://github.com/smileyborg/PureLayout
This method finds the commonSuperview using the firstItem or secondItem and removes the constraint from the correct view.
It resulted in this one liner:
containerTopConstraint.autoRemove()
No, not that I'm aware of. The automatic managing of the host view only came in iOS8.
An ugly implementation could loop over all constraints of all views to finde the view where it is on.
But normally it shouldn't be to hard to manage the constrains in a way that you always know on which view they are defined.

When can I activate/deactivate layout constraints?

I've set up multiple sets of constraints in IB, and I'd like to programmatically toggle between them depending on some state. There's a constraintsA outlet collection all of which are marked as installed from IB, and a constraintsB outlet collection all of which are uninstalled in IB.
I can programmatically toggle between the two sets like so:
NSLayoutConstraint.deactivateConstraints(constraintsA)
NSLayoutConstraint.activateConstraints(constraintsB)
But... I can't figure out when to do that. It seems like I should be able to do that once in viewDidLoad, but I can't get that to work. I've tried calling view.updateConstraints() and view.layoutSubviews() after setting the constraints, but to no avail.
I did find that if I set the constraints in viewDidLayoutSubviews everything works as expected. I guess I'd like to know two things...
Why am I getting this behavior?
Is it possible to activate/deactivate constraints from viewDidLoad?
I activate and deactivate NSLayoutConstraints in viewDidLoad, and I do not have any problems with it. So it does work. There must be a difference in setup between your app and mine :-)
I'll just describe my setup - maybe it can give you a lead:
I set up #IBOutlets for all the constraints that I need to activate/deactivate.
In the ViewController, I save the constraints into class properties that are not weak. The reason for this is that I found that after deactivating a constraint, I could not reactivate it - it was nil. So, it seems to be deleted when deactivated.
I do not use NSLayoutConstraint.deactivate/activate like you do, I use constraint.active = YES/NO instead.
After setting the constraints, I call view.layoutIfNeeded().
Maybe you could check your #properties, replace weak with strong.
Sometimes it because active = NO set self.yourConstraint = nil, so that you couldn't use self.yourConstraint again.
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}
I believe the problem you are experiencing is due to constraints not being added to their views until AFTER viewDidLoad() is called. You have a number of options:
A) You can connect your layout constraints to an IBOutlet and access them in your code by these references. Since the outlets are connected before viewDidLoad() kicks off, the constraints should be accessible and you can continue to activate and deactivate them there.
B) If you wish to use UIView's constraints() function to access the various constraints you must wait for viewDidLayoutSubviews() to kick off and do it there, since that is the first point after creating a view controller from a nib that it will have any installed constraints. Don't forget to call layoutIfNeeded() when you're done. This does have the disadvantage that the layout pass will be performed twice if there are any changes to apply and you must ensure that there is no possibility that an infinite loop will be triggered.
A quick word of warning: disabled constraints are NOT returned by the constraints() method! This means if you DO disable a constraint with the intention of turning it back on again later you will need to keep a reference to it.
C) You can forget about the storyboard approach and add your constraints manually instead. Since you're doing this in viewDidLoad() I assume that the intention is to only do it once for the full lifetime of the object rather than changing the layout on the fly, so this ought to be an acceptable method.
You can also adjust the priority property to "enable" and "disable" them (750 value to enable and 250 to disable for example). For some reason changing the active BOOL didn't had any effect on my UI. No need for layoutIfNeeded and can be set and changed at viewDidLoad or any time after that.
The proper time to deactivate unused constraints:
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
self.myLittleConstraint.active = NO;
}
Keep in mind that viewWillLayoutSubviews could be called multiple times, so no heavy calculations here, okay?
Note: if you want to reactive some of the constraints later, then always store strong reference to them.
When a view is being created the following life cycle methods are called in order:
loadView
viewDidLoad
viewWillAppear
viewWillLayoutSubviews
viewDidLayoutSubviews
viewDidAppear
Now to your questions.
Why am I getting this behavior?
Answer: Because when you try to set the constraints on the views in viewDidLoad the view does not have its bounds, hence constraints cannot be set. It's only after viewDidLayoutSubviews that the view's bounds are finalized.
Is it possible to activate/deactivate constraints from viewDidLoad?
Answer: No. Reason explained above.
I have found as long as you set up the constraints per normal in the override of - (void)updateConstraints (objective c), with a strong reference for the initiality used active and un-active constraints. And elsewhere in the view cycle deactivate and/or activate what you need, then calling layoutIfNeeded, you should have no issues.
The main thing is not to constantly reuse the override of updateConstraints and to separate the activations of the constraints, as long as you call updateConstraints after your first initialization and layout. It does seem to matter after that where in the view cycle.

Resources