How to do this correctly
Your UIViewController
subclass should override the method viewWillLayoutSubviews
, see also here.
When this method is called, the viewController's view
has its correct size and you can make any necessary adjustments to subviews prior to the layout pass over the subviews.
Swift
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
NSLog("bounds = \(self.view.bounds)")
}
Obj-C
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
NSLog(@"bounds = %@", NSStringFromCGRect(self.view.bounds));
}
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.
As you see from the emphasised part, this method is called every time the view controller's view changes size, as well as when the view first appears. This lets you respond correctly to rotation and other bounds change events
Several ways that don't work
A few approaches suggested in other answers don't work well, or have serious drawbacks. I urge you to avoid these approaches and I'll go through them to discuss the reasons you should avoid them.
viewDidLoad
and viewWillAppear
– during these calls view
does not yet have its final size. The size you get will only ever be correct by pure chance, so as to mislead you.
viewDidAppear
– this is too late. Your view is already on screen and visible to the user. Making changes here will cause visible changes / abrupt glitches and will look amateurish. Once again, please – for your sake, for my sake, for everyone's sake: don't do it! You're better than that and so are your users.
UIScreen.mainScreen.bounds.size
– this is extremely low level. You're implementing a UIViewController
and the size of its view depends on the controllers it is nested in (navigation, tab, paging, any custom controllers, etc), how the device is rotated, and potentially, how the screen has been split up for multitasking. So, while you might be able to compensate for all these and calculate the final size of your view, you'll end up with complex and brittle code that can easily break if Apple decide to change any of these metrics. UIViewController
will do all this for you if you just override viewWillLayoutSubviews
.
Other than not providing correct information, these problematic approaches will not help you with auto-rotation or other events that cause the view controller's view to change size, such as multitasking gestures. This is something you really want to handle smoothly.
So please: be a champ. Do it the right way. Use viewWillLayoutSubviews
. Your implementation will be called for every size change, and your users, future self and team members will celebrate you for it. Bravo!
Further tips
When viewWillLayoutSubviews
is called, the only view in your hierarchy that will be resized to its final size is viewController.view
. The give away for this is in the name of the method. It's telling you view…
(your view controller's root view
) …WillLayout…
(really soon now, but it's not happened yet) …Subviews
(everything else in its hierarchy under the root view).
So subview layout has not happened yet. Every child under the root does not yet have a valid final size. Any size information you query from the child views will be at best completely wrong.
More likely, and enormously worse, it will be misleadingly correct.
It happens to be what you expect and need due to a default at this size and orientation, or due to your storyboard settings. But this is only by chance and isn't something you can rely on with different device sizes or orientations.
If you need to be told when a particular subview changes size, and know its exact final size, you should generally override the layoutSubviews
method of that particular UIView
subclass.
viewWillLayoutSubviews
worked but had a delay (about one second) that I didn't like. I triedviewDidLayoutSubviews
and it worked properly. Keep in mind that viewDidLayoutSubviews may be triggered multiple times depending on what you do with your subviews so you may need some extra checks. developer.apple.com/documentation/uikit/uiviewcontroller/… – TomboloviewDidLayoutSubviews
is called afterviewWillLayoutSubviews
, so it can't have any less delay. Does that make sense? I suggest that the delay that you encountered must be due to something else going on – perhaps an interaction with animation? – Prismatic