Understanding the UIViewController Lifecycle in iOS

A concise, handy, and comprehensive guide for iOS developers.

Medi Assumani
5 min readApr 5, 2022
Figure 0.0: A diagram of a UIViewController lifecycle. Courtesy of Apple.

Overview

Almost each screen that an iPhone user sees on their phone is a UIViewController, which is a class that manages the contents of that screen (buttons, images, texts, etc..), accurately calculate their sizes, responds to the user interactions (touching, dragging, long pressing), and so forth. Today I wanted to explain the most important methods that makes up the lifecycle of a UIViewController, that is from the second a user intends to view that screen to the moment they dismiss it. I believe it is crucial for each iOS Engineer to really understand it in order to make educated decisions as to where and when to execute a block of code when a screen is presented. Let’s get into it.

1. init

This is more of a OOP method rather than a lifecycle’s, but is the first that gets called immediately after a UIViewcontroller object has been initialized. One thing to keep in mind is that init() is only called once during the lifetime of its view view controller(from when created to when it is released from memory), so make sure whatever code that’s in there needs to be executed only once.

Figure 1.0: UIViewController init method.

2. willMove & didMove

willMove() is the first lifecycle method that gets triggered. This happens up until a child view controller is added to a parent/super view controller. For example, using addChild() to add view controller onto the current hierarchy will automatically call that child’s willMove() function to notify it that it’s about to be added onto the view hierarchy.

Figure 2.0: Using willMove & DidMove

Here are a few things to keep in mind: the child view controller implementation of didMove(toParent: ) will NOT gets triggered unless it is explicitly called by the parent class(line 7). Secondly, Calling removeFromParent() will trigger the willMove() function because it causes that view controller to transition/go to another super/parent view.

3. loadView

loadView() is triggered when the the view controller’s view object is being created. When using storyboards, this is the method that will load your nib and set it to the view controller’s view object. However, if you’re setting up your User Interface programmatically, this method would simply create an empty UIView object and assign to the view controller’s view object. You can also override(but NOT invoke it explicitly), this method to configure your views hierarchy manually.

Figure 3.0: Making use of loadView to customize the UIViewController view object at its inception.

4. viewDidLoad

viewDidLoad() is the most common and familiar lifecycle method in a UIViewController. When the view controller’s view object is loaded/created into memory after loadView() completes its execution, UIViewController tells our class “okay, this controller has a view now and is ready to be customized to our liking”. This means we can use viewDidLoad() to add subviews, customize other UI components(text, buttons, images), make api calls, and so so forth.

Figure 4.0: Using viewDidLoad

One thing to keep in mind is that viewDidLoad() only gets called once in the entire lifetime of your UIViewController class, so make sure the code you put in here needs to only run once as well.

5. viewWillAppear

viewWillAppear() is triggered to notify your UIViewController class that it’s almost ready to be made visible to the user. You can use this method to carry out extra tasks that needs to be done before the user sees the screen.

Figure 5.0:

One thing to keep in mind is that this method can be called multiple times during the lifetime of your UIViewController. An example of that is when a user switches back and forth between different tabs, which will require each view controller to appear each time that happens. So make sure the code you put inside this function doesn’t mind running more than once.

6. viewDidLayoutSubviews

Now that you’ve added your subviews, it will cause the bounds of the view controller’s view object to update. That’s when viewDidLayoutSuviews() gets triggered, as you will now have access to the actual x, y, width, and height of your view controller’s view. If setting up your subviews programmatically, you can override this method to update their sizing and positions.

Figure 6.0: Using viewDidLayoutSubviews.

Another thing to keep in mind is that viewDidLayoutSuviews can also be triggered when a user rotates their device from portrait to landscape mode(and vice versa), as that would cause the bounds of the view to update. Additionally, forcing a layout using setNeedsLayout() or layoutIfNeeded() will also trigger viewDidLayoutSuviews() to execute in order to adjusts accordingly.

7. viewDidAppear

After all the subviews are positioned, sized, and laid out on the view, viewDidAppear() is triggered to notify your class that the user can now visibly see everything on the screen.

Figure 7.0:

8. viewWillDisappear & viewDidDisappear

When a user is about to transition from one screen to another, viewWillDisappear() and viewDidDisappear() gets called to notify the current view controller that it will soon be dismissed. This can be a great place to do any clean up(i.e: cache specific to this view controller) or cancel any pending or long running tasks(api request, queued job, etc…).

Figure 8.0: Using viewWillDisappear() and viewDidDisappear()

9. deInit

Just like init() is the first method to get called, deinit() is the last one as it notifies your view controller class that it has been removed and released from memory.

Figure 9.0: deinit

Here are a few things to keep in mind: If the code within your deinit() method never gets called(aka your view controller never gets deallocated), there is a very good chance you have a retain cycle. Your view controller is strongly referenced in a closure, by a protocol, or by another class that’s still alive. Another thing to keep in mind is that you need to be very careful about accessing a class property within the deinit(), as at that point that property might be nil, cause a race condition, or crash your app.

Conclusion

There you have it. I’d strongly encourage you to dig into the official UIViewController documentation from Apple or the Swift open sourced code on Github. There are other methods and properties that have not been mentioned in this article but are just as important. Thanks for reading, If you learned a thing or two in this article, don’t forget to support by leaving some claps 👏 or leave a comment below with any questions you might have.

About the Author :

Hi, I’m Medi! I enjoy writing about swift, iOS development, and tech in general. I work at BuzzFeed as an iOS Engineer and studied computer science. You can get in touch with me through email , LinkedIn, or Github.

--

--

Medi Assumani

iOS Engineer @ Doordash. Writing about Mobile development and software engineering in general. Views and contents here are my own, of course.