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.
Related
I had a conversation with my colleagues about setNeedsLayout() and layoutIfNeeded().
Starting from the conclusion, my colleague says
setNeedsLayout() 'may' or 'should' be called before layoutIfNeeded(), because layoutIfNeeded() may not conduct layout if we don't set the 'layout flag' to true by calling setNeedsLayout().
Whereas, my thought is, calling both of them in the same place will lead to the same result as we call layoutIfNeeded() only. Because...
What I know about them is, as described here and here,
setNeedsLayout() is just invalidating the current layout and then the coming update cycle would take care of the rest.
layoutIfNeeed() is updating the layout immediately, so don't want to the coming update cycle.
Simply saying, async call and sync call. And that's it.
If there is no animation, I don't recall I've seen a lot of cases I had to use layoutIfNeeded(Perhaps one or two). Most of the cases, it worked quite well only with setNeedsLayout(). Moreover, if there is a case that needs to call both of them in the same place, I would rather say it's a bug from UIKit.
Can someone explain me which one is correct, and why?
First, a little discussion what these methods do, the answer to your question is below that.
From the documentation of setNeedsLayout():
This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated.
Usually, you need to call this if there's some external factor affecting the layout that UIKit doesn't know about (e.g. old fashioned manual layouting code by overriding layoutSubviews()). It doesn't trigger an immediate re-layout, that usually only happens on the next main runloop iteration. This allows you to mark several views as needing layouts, you can call it on the same view multiple times within the same runloop iteration, but UIKit only does the actual layouting once (as I just noted, usually on the next runloop iteration).
But if you need the layout to get settled right now (e.g. because you need to measure its result), you need to call layoutIfNeeded().
If no layout updates are pending, this method exits without modifying the layout or calling any layout-related callbacks.
This is a crucial point about layoutIfNeeded(): if the layout system doesn't know there are pending changes, it simply won't do any. Calling setNeedsLayout() is the way we tell the layout system that there are changes that require a layout run.
If you only use auto-layout and manipulate just the constraints of the view you are about to re-layout, you can skip setNeedsLayout() since that has already been called indirectly; but setNeedsLayout() is very cheap, so there's no harm in calling it if you're unsure.
So, after calling setNeedsLayout(), do you need to call layoutIfNeeded()? To simply update the view on screen, no, you do not need to call layoutIfNeeded() since UIKit will do so on its own on the next main runloop iteration. In fact, you might negatively impact render performance if you always call layoutIfNeeded() even though your code doesn't need the updated layout right away since this can result in multiple layouting runs within the same runloop iteration.
But if you rely on the layout information immediately (for example, you need to measure the height of a view), you do need to call it. Just beware to have your design not do this too often.
Your comparison with "async" and "sync" is indeed somewhat fitting: in a way, you can see setNeedsLayout() as triggering an asynchronous operation and layoutIfNeeded() "waits" until it has happened (that's not what's actually going on, but from a certain point of view that is the observable effect).
Does anyone know why UITextView.layoutSubviews() is not called when rotating a device to portrait mode?
When rotating to landscape mode, these are called:
UIViewController.viewWillTransition
UIViewController.viewDidLayoutSubviews
UITextView.layoutSubviews
UILabel.layoutSubviews
But when rotating back to portrait, the UILabel.layoutSubviews() is called, but not the UITextView.layoutSubviews. This is in an empty project with no other code apart from traces in these methods.
layoutSubviews is usually called when setNeedsLayout() is invoked already in the previous invocation of run loop.
If the layout system does not think it needs to be called, it will not be called.
Ideally you should not override this function. You should just call setNeedsLayout() after making superview changes, and let the layout system call the default implementation of this function. Morever, you should define your subview layout needs inside auto-layout so it is able to get correct values from there.
If you want immediate update, you should call layoutIfNeeded().
This is because this is one of those methods that are called arbitrarily by UIKit framework and it may not be ideal for your layout needs.
There are 2 separate things here.
Understanding layoutSubviews(). I.e. when and where to use it.
How to achieve what you want to do the right way. I.e. doing something with the UITextView at device rotation.
About the layoutSubviews(), you should not put any logic here as your view is not having any sub views.
You may say that we expect iOS to call it, so we can put some implementation here, but again, that is not the right way. layoutSubviews() is not meant to alter the view itself, but just laying out sub views.
I would recommend reading more on layoutSubviews(). I learnt from here, when I started learning iOS.
Now to achieve what you want to do, i.e. do something at the time of device rotation, you proper way is to use viewWillTransition(to:with:) method of UIViewController.
In this method, you can put logic to do something just before the transition will happen.
And you can also put logic which will execute during the transition OR after the transition completes, using the UIViewControllerTransitionCoordinator parameter passed to viewWillTransition(to:with:)
Hope this helps!
I have read some articles that what's the difference between setNeedsLayout and layoutIfNeeded, while what I am focusing is:
1. do I need to call these two method together if I want layout immediately, because I saw this kind of combination so many times
2. when do I need to call setNeedsLayout? upon my understanding, If I change view's frame, it will update layout during next cycle, that I don't have to call setNeedsLayout explictly
How these things work is through invalidation to remove redundancy. A view will contain information if needs to layout or not.
So calling setNeedsLayout will just set some internal boolean value needsLayout to true. Once layoutIfNeeded is called it will check this boolean value
if needsLayout {
needsLayout = false
doMagic() // Calls layoutSubviews at some point
}
Why this is designed this way is because multiple calls may invalidate layout but we want to layout it only once or as fewer times as possible.
In most cases you will not need to call setNeedsLayout because most changes already do that for you. For instance you may change a constraint value and invalidation is done for you. All you need is to call layoutIfNeeded and your views will update. To be more correct you don't even need to call layoutIfNeeded as it will do that for you in the next cycle. But you will need to call it if you want the change animated for instance and you need to do that in animation block.
myViewConstraint.constant = 40.0 // Will already call setNeedsLayout
UIView.animate(withDuration: 0.3, animations: {
myView.layoutIfNeeded()
})
So changing constraints does nothing but change the information on how the view(s) should be layout. Only the call to layoutIfNeeded will actually use those values and change layout. That is why you only need to put that in the animation block (it is not wrong to put it all in the block though).
To be fair there have been some changes where now by default (you may disable it) animate methods already layout your views by themselves so you can do with even less code but that is not the point at the moment.
So:
You do not need to call the 2 methods together to layout immediately. If the view layout is already invalidated (which is in most cases) then layoutIfNeeded is enough. But note that setNeedsLayout is as trivial as setting a boolean internally to true so there is no harm in calling it, just a precaution. So calling both is safer. Calling setNeedsLayout alone will do nothing "immediately" though.
Hopefully you never need to call setNeedsLayout. There are some complex situations where you need to explicitly invalidate layout and there are a few possible UI bugs. In all other cases this will be done for your. But note that if you come to a situation where you need to call this "it will update layout during next cycle" will not be true. Until the view layout is invalidated it will not layout at all.
I am not sure where setNeedDisplay fits in your question (it is only in your title) but this one works the same way but is a bit more complicated. It will invalidate its content and force it to redraw, call drawRect. This must occur during its drawing pipeline, not just anytime so you may not explicitly call it to redraw. And if you do nothing will happen (maybe crash) since it will have no context to draw on. If you override drawRect and you resize your view it will try to cache your drawn content and use contentMode to resize the drawing. By default it is set to scaleToFill which means your content will be stretched as view size changes. You will need to call setNeedDisplay in order for your drawRect to be called again and you may redraw content accordingly.
I found what I want to ask, when should I call setNeedLayout, when should not. since the aim to call setNeedLayout is to call layoutSubviews, so for situations as follows, I don't have to do call setNeedLayout
Resizing a view
Adding a subview User
scrolling a UIScrollView
(layoutSubviews is called on the UIScrollView and its superview)
User rotating their device
Updating a view’s constraints
I usually set all my auto layout code in the updateCOnstratins method of my view controller for the constraints of all the subclasses defining the view. Then in the subviews I place my constraints in the updateConstraints methods there. This makes me have a property of every single view in my class so I can reference it later on after I set translates.... to false. But Im reading that you don't have to set it in updateConstraints. Just not I read an article where the person says an apple engineer said that if the constraints are only made once then you can put them pretty much where ever. Yet, if you have constrains that change during the views lifecycle you place them in updateConstraints? Here are the links http://swiftandpainless.com/where-to-put-the-auto-layout-code/ http://swiftandpainless.com/dont-put-view-code-into-your-view-controller/.
So where should It go? Was this just an old way of doing this and now it has changed?
What you said in your post is what you would generally want to do. Put any constraints that might change in updateConstraints. This also means you should keep a reference to them to be able to update them or remove/replace them. Any static ones can be put after your initialization code (the init method of a UIView or the viewDidLoad method of a UIViewController, for instance). The only real requirement there is you can only add constraints to views that are actually in a view hierarchy together, so anytime after you've added the appropriate views would be fine.
There is usually no reason not to put your constraint creation code in viewDidLoad, which has the advantage of being called only once. For constraints that change, I like to associate that code with whatever directly precipitates the change, such as a change in size class or the removal or insertion of a view.
My class extends Manager and calls setPositionChild(Field f) to change f's position. After calling setPositionChild() method, how do I apply the position(i.e. re-layout and re-paint) so I can see the changes?
I tried to call invalidate(), which did not work.
invalidate() just forces a repaint, it doesn't redo the whole layout, as you noticed.
You can force a relayout by going up to the parent Screen object, and calling invalidateLayout(). Forcing the layout will almost certainly call setPositionChild() on the field you are trying to move, so you will want to make sure the new field position is preserved by the manager layout code. This may mean you need to write a custom manager.