What's exactly viewDidLayoutSubviews? - ios

I was reading the description of viewDidLayoutSubviews of UIViewController:
Called to notify the view controller that its view has just laid out its subviews [...] However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout [...].
For me, it means: "Called when the layout of subviews is complete, but actually this is not true". So what's really behind viewDidLayoutSubviews?

When bounds change for a ViewControllers View, this method is called after the positions and sizes of the subviews have changed.
So this is our chance to make changes to view after it has laid out its subviews, but before it is visible on screen.
Any changes that depending on bounds has to be done, we can do here and not in ViewDidLoad or ViewWillAppear.
While ViewDidLoad & ViewWillAppear, the frame and bounds of a view are
not finalised. So when AutoLayout has done it's job of fixing mainView and
it's subviews, this method is called.
When using autolayout, framework does not call layoutSubviews every time. This is called in these cases.
Rotating a device: only calls layoutSubview on the parent view (the responding viewControllers primary view)
Its own bounds (not frame) changed. (The bounds are considered changed only if the new value is different, including a different origin.)
A subview is added to the view or removed from the view.
Your application forces layout to occur by calling the setNeedsLayout or layoutIfNeeded method of a view.
Scrolling a UIScrollView causes layoutSubviews to be called on the scrollView, and its superview.
Note:
The call for viewDidLayoutSubviews also depends on various factors like autoresize mask, using Auto-Layout or not, and whether view is in view hierarchy or not.
For any other clarification, check When is layoutSubviews called?

viewDidLayoutSubviews will be called when
When the bounds change for a view controller's view, the view
adjusts the positions of its subviews and then the system calls this
method.
For example you have set constraints of your view then you want to update the frame for your subview in viewDidLoad(), which will not make any impact as in viewDidLoad() your constraints are not properly set, they will get properly set when viewDidLayoutSubviews get called, now you want to update the frames of your subview, then you can do that in this method as this method get called only after all the constraints of your view are properly set.

Related

When are the ViewWillLayoutSubviews and ViewDidLayoutSubviews methods called?

When do the ViewWillLayoutSubviews and ViewDidLayoutSubviews methods of UIViewController get called?
What is the status of the view's frame before willLayoutSubviews?
From the documentation:
When a view's bounds change, the view adjusts the position of its subviews. Your view controller can override this method to make changes before the view lays out its subviews. The default implementation of this method does nothing.
viewWillLayoutSubviews gets called anytime your view controller's view has its bounds changed. This happens when the view is loaded, when a rotation event occurs, or when a child view controller has its size changed by its parent. (There are probably some other situations, too). If there is anything you need to update before that view lays itself out (and before your constraints are re-applied) you should do it here. you should generally not update constraints here, because updating constraints can cause another layout pass.
viewDidLayoutSubviews is called once all of your subviews have been laid out. If you need to fine-tune that layout by manually adjusting frames, for instance, this would be the place to do it.
View controller lifecycle can be a bit confusing, but it's worth trying to really understand if you're going to be doing much iOS development. This article is a really good overview.

setNeedsDisplay breaks UIView's Auto Layout animations

I have a UIViewController that contains two views (from two child view controllers): A top content view, and a bottom menu popup. The constraints are set up so that as the bottom menu expands, the top content view is squashed:
All the constraints are set up correctly so the subviews all layout correctly when the menu is expanded and collapsed. It even works correctly with a UIView animateWithDuration: block (I am calling [self.view layoutIfNeeded] to update the layout of the subviews during animation).
However, one of the views within the Content View area is a custom view containing a drawRect: method. Every time this view updates, I am calling setNeedsDisplay. As soon as setNeedsDisplay is called for the first time, the animations for this view break and will not update its frame until setNeedsDisplay is called again. I cannot get it to animate with all the other views! Comment out the drawRect: method, or comment out the [self setNeedsDisplay], and the view animates with the parent views again perfectly.
Does anyone know what I might be doing wrong?
I think you just should let the iOS do the work for you.
If your view needs to be redrawn often, set the view's contentMode:
yourView.contentMode = UIViewContentModeRedraw;
And it should redraw when needed.

layoutSubview resetting properties of UILabel

I have a ViewController with a UIScrollView on it. On this VC I programatically adjust the frame of a UILabel which is inside the scroll view. This is done on viewDidLoad. This UILabel comes from the VC's xib file, it is not created programatically, only its frame changed.
When I transition from this VC to another, then back, the UILabel's frame gets reset to the XIB's state. It's text however is not reset, it stays the same text I set before.
My investigation tells me that this occur on layoutSubviews, as the UILabel's properties are correct on willLayoutSubviews, then reset on didLayoutSubviews when moving back to the VC.
Is this expected behaviour? Is there a reason why the label's text remains but the frame gets reset? Is this because the UIScrollView calls layoutSubviews on its parent view whenever scrolling?
Thanks
I see two problems in your description. First, you said this:
On this VC I programatically adjust the frame of a UILabel which is inside the scroll view. This is done on viewDidLoad.
It is generally a bad idea to modify view frames in viewDidLoad, because the system's layout phase (during which layoutSubviews messages are sent) hasn't happened yet. In viewDidLoad, your view's frame hasn't been adjusted for the current device's screen size and interface orientation yet.
Second, you said that you're using autolayout. The autolayout system sets the frames of views during the layout phase. The layout phase can be triggered by many different events, including (as you've discovered) the appearance and disappearance of views.
In order to make your adjustment to the label's frame “stick”, you need to modify the constraints that control the label's frame. One way to do this is to create an outlet of type NSLayoutConstraint on your view controller for each constraint that you need to modify, and connect these outlets to the constraints in your xib. Then in your view controller's viewWillLayoutSubviews, you can modify the constant property of each constraint as necessary. (Ironically, constant is the only modifiable property of an NSLayoutConstraint.)

layoutSubviews called twice when rotating

When my main view rotates I want to re-arrange the sub views, so in my ViewController, I override
willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
and set the frames of the subViews in there. This is all well and good, but in the subViews I have also overridden
layoutSubviews
so they layout themselves correctly. But the problem is this now gets called twice - presumably once when I set the Frame in willAnimateRotationToInterfaceOrientation and once because of the rotation. (If I don't set the Frame it gets called once.)
Surely it's the responsibility of the ViewController to layout the frames so this seems like a design flaw - what's the solution so layoutSubviews is only called once?
I had the same question. I found this page to be helpful for me. Is this useful for you?
EDIT:
Here is the summary of the page (copied):
init does not cause layoutSubviews to be called (duh)
addSubview causes layoutSubviews to be called on the view being added, the view it’s being added to (target view), and all the subviews of the target view
setFrame intelligently calls layoutSubviews on the view having it’s frame set only if the size parameter of the frame is different
scrolling a UIScrollView causes layoutSubviews to be called on the scrollView, and it’s superview
rotating a device only calls layoutSubview on the parent view (the responding viewControllers primary view)
removeFromSuperview – layoutSubviews is called on superview only (not show in table)

When using Storyboards, why does viewWillAppear not draw my subviews and viewDidLayoutSubviews does

When using Storyboards, why does viewWillAppear not draw my subviews and viewDidLayoutSubviews does and more importantly to access the frame.size value from subviews of subviews I have to call [self.scroller layoutIfNeeded] inside of viewDidLayoutSubviews? I'm interested in understanding the page life cycle of a view controller and what changed in going from xibs to storyboards.
Storyboards are actually implemented as collections of xib files, with additional information about transitions (segues) between them. So the view controller life cycle should not be radically different if we're just talking about a single view controller.
It's very difficult to answer your specific question without understanding how your view controller and its view hierarchy are set up. It sounds like you have a view inside of a scroll view and you want to know when you can access its frame property.
UIKit follows these steps (roughly):
It loads all the views described in the storyboard/xib file and connects all the actions and outlets as needed. viewDidLoad is called after this step.
It calls viewWillAppear: to indicate that it is about to display the view.
It adds the view to the window, sizing it to fit. The sizing propagates down the view hierarchy, so each view lays out its subviews (if it is configured to autoresize subviews). These changes occur inside an animation block, so once everything is set up the user sees the new view animated into place.
Once animations are complete, viewDidAppear: is called.
It's possible you are seeing something strange if a view has autoresizesSubviews set to NO; that may be why you have to call layoutIfNeeded on self.scroller. Note that the documentation for layoutIfNeeded says:
When this message is received, the layer’s super layers are traversed until a ancestor layer is found that does not require layout. Then layout is performed on the entire layer-tree beneath that ancestor.
So it could potentially be triggering the layout of other unrelated views.

Resources