An Examination of When to Use viewDidLoad, viewWillAppear, and viewDidLayoutSubviews

Overview

A common question is when to override and add code in viewDidLayoutSubviews, versus viewDidLoad and viewWillAppear. Let’s take a look at all three and consider when we should override viewDidLayoutSubviews.

You can download the sample project for this brief tutorial from Github.

First, an overview of the three methods and their intended usage.

viewDidLoad

viewDidLoad is for configuring anything that you did not configure in a XIB or Storyboard. It is called after the view controller has loaded its view hierarchy into memory from XIB or Storyboard. It is also called when a view was created programmatically in the loadView method, although when using loadView you should not need to use viewDidLoad since you’ve created your view programmatically and there is no need to separate part of that code into viewDidLoad.

When this method is invoked, you know that IBOutlets are now connected, but the view has not yet been laid out so at this point is where you should do any view customization that could not be done in Interface Builder.

It is important to remember that once your view is loaded and in a navigation stack, moving from the view to another screen and back again does not result in viewDidLoad being called again, so do not place code here that needs to be updated when a view controller is about to become active.

viewWillAppear

This method is called to tell a view controller to get it’s view ready to display on screen. You should override this method to perform any setup related to the state of your application and the data that will be displayed for the view. Some examples include data values for controls, UI customizations such as colors or text that are dependent on data, and selection state of controls.

viewDidLayoutSubviews

With everything we can achieve by the two previous methods, what is the reason for viewDidLayoutSubviews and why would we override it? When the bounds change for a view controller’s view, this method is called after the positions and sizes of the subviews have changed. This is our chance to make changes to a view after it has laid out its subviews, but before it is visible on the screen. The key here is the change to bounds. Anything dependent on the bounds that needs to be done to the view must be in this method and not in viewDidLoad or viewWillAppear,because the frame and bounds for a view are not finalized until Auto Layout has done its job of laying out the main view and subviews, and this method is then called.

A Concrete Example

Suppose we have implemented a circular button that should be 50 points width and height on any compact width devices, and 100 points for regular width devices. The algorithm in this tutorial for transforming a regular UIButton into a circular button uses the bounds of the original button, and applies a cornerRadius to it. Here is what the size class definition for the Height Constraint looks like in the Storyboard.

Here is the method for determining the radius of the button based on the bounds size.

func configureButton() { editButton.layer.cornerRadius = 0.5 * editButton.bounds.size.width editButton.layer.borderColor = UIColor(red:0.0/255.0, green:122.0/255.0, blue:255.0/255.0, alpha:1).CGColor as CGColorRef editButton.layer.borderWidth = 2.0 editButton.clipsToBounds = true }

This function can only be called once the bounds of the button are known, and in the case of size classes, this does not happen until Auto Layout has finished its layout pass. viewDidLayoutSubviews is the exact place for additional code that must depend on the bounds being set by Auto Layout. In this method it is safe to configure the cornerRadius of the button using the bounds of the button.

You can see in the code below that this function is called from within viewDidLayoutSubviews.

override func viewDidLoad() { super.viewDidLoad() //configureButton() // NOT HERE } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) //configureButton() // NOT HERE } override func viewDidLayoutSubviews() { configureButton() // HERE AFTER BUTTON BOUNDS ARE KNOWN }

Try changing the call to configureButton so it is in viewDidLoad or viewWillAppear, run it on an iPad, and you will see that the button did not become circular, because the algorithm has been applied before the bounds were set.

It can be tricky to understand where to place certain code when considering these three methods, but once you understand their usage it becomes much easier.

I have two other related posts you might wish to read: setNeedsDisplay And drawRect Explained, as well as setNeedsLayout vs layoutIfNeeded Explained.

Thanks for reading and I hope this post has helped.