ViewController Lifecycle is the sequence of methods that UIKit automatically calls when managing screens in iOS. According to Apple Documentation, each UIViewController goes through a predictable set of states: from View creation to its appearance and disappearance. Understanding the order and purpose of these methods is a necessary condition for stable iOS app operation.
Key Takeaways
ViewController Lifecycle is a set of methods that UIViewController receives from UIKit throughout its existence. Each screen in an iOS app sequentially goes through the stages of creation, View loading, appearing on screen, disappearing, and memory deallocation. UIKit automatically calls the corresponding methods at each stage, and the developer overrides them to add custom logic.
The UIViewController architecture is fundamental to UIKit and remains relevant even in the SwiftUI era — many projects still use the classic approach or a hybrid architecture. Understanding the Lifecycle allows you to predict when subviews are available, when it is safe to modify the layout, and which operations to perform when the screen appears or disappears.
Each lifecycle method has a specific purpose: some are called once during the entire controller’s lifetime, others — on every appearance or disappearance. Mixing logic between methods leads to hard-to-find bugs: memory leaks, incorrect data updates, and unnecessary network requests.
Six methods form the complete UIViewController lifecycle. The call order is fixed and does not depend on the navigation method — push, present, or unwind segue all follow the same schedule.
loadView is the first method of the cycle, called when the controller’s View does not yet exist. If you use Storyboard, UIKit automatically loads the View from the xib file. When creating the interface programmatically, you override this method by assigning the root View manually. In most projects, loadView is left untouched — work is done in viewDidLoad.
Overriding loadView is only needed in specific cases: when the entire interface is created in code without Storyboard, or when the root View needs to be of a non-standard class. Apple recommends not calling super.loadView when overriding — you take full responsibility for creating the View.
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
viewDidLoad is the most commonly used method of the cycle. It is called once after the View is loaded into memory but is not yet displayed on screen. Here you configure subviews, populate tables with data, register cells, and subscribe to notifications that last the entire lifetime of the controller.
An important feature: viewDidLoad is not called again when the screen is shown again. If you need to update data every time the screen appears — use viewWillAppear. Place only one-time operations in viewDidLoad that are required for basic configuration.
viewWillAppear is called each time right before the View becomes visible to the user. This method receives an animated parameter indicating whether the appearance is animated. Here you update data, reload tables, configure the NavigationBar, and hide or show elements depending on the application state.
Use viewWillAppear for state synchronization between screens: if the user could have changed data on the previous screen, this method is the right place to update the interface. Each call to viewWillAppear precedes the screen appearance, even when returning from a child controller.
viewDidAppear notifies that the View has fully appeared on screen and all transition animations are complete. At this point, the screen is ready for interaction — the user sees the full interface and can interact with it. This method is suitable for starting animations that should begin after appearance, starting timers, and tracking analytics impressions.
Unlike viewWillAppear, viewDidAppear guarantees that the screen is not only visible but also fully rendered. If you start an animation in viewWillAppear, some frames may be skipped because UIKit has not yet completed the transition. For smooth animations, use viewDidAppear.
viewWillDisappear is called before the View disappears from screen — when transitioning to another controller, closing a modal window, or suspending the app. This is the right place to save state, unsubscribe from notifications, stop active processes, and release resources that are not needed when the screen is not visible.
It is important to remember: viewWillDisappear does not guarantee that the View will ultimately disappear — the gesture may be cancelled. Therefore, save critical data also in viewDidDisappear, which is called only after the actual disappearance.
viewDidDisappear completes the appearance and disappearance cycle. It is called after the View is already hidden from the screen. In this method, animations are finally stopped, temporary objects are removed, and data saving initiated in viewWillDisappear is confirmed.
This method also precedes the controller’s deinit — if your UIViewController is being destroyed, viewDidDisappear will be the last Lifecycle method before deinit is called. Use it for final cleanup that should happen before the object is destroyed.
The sequence of calls depends on how the screen appears: for the first time, when going back, or when presented modally. Let’s consider three main scenarios from UIKit’s perspective.
When a screen appears for the first time, UIKit goes through the full creation cycle: loadView is called, then viewDidLoad, after which the appearance animation starts. During the animation, viewWillAppear is called, and after completion — viewDidAppear. This is the only scenario where all methods from loadView to viewDidAppear fire sequentially.
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad — View loaded into memory")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear — About to appear")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear — Screen fully visible")
}
When the user goes back to a previous screen, UIKit does not call viewDidLoad again — the View is already loaded into memory. Instead, only viewWillAppear and viewDidAppear are called on the returning screen, and on the current one — viewWillDisappear and viewDidDisappear. loadView and viewDidLoad are skipped since the screen already exists in the navigation stack.
Modal presentation follows the same rules: the new controller goes through the full cycle on first appearance, while the current one receives viewWillDisappear and viewDidDisappear. On dismiss, the order is reversed: the returning controller gets viewWillAppear and viewDidAppear again, while the dismissed one gets the final methods. This behavior is uniform for all transition types in UIKit.
Let’s look at four key scenarios where understanding the Lifecycle directly impacts code quality and user experience. For each scenario, we provide an example with recommendations.
viewDidLoad is the place for initial setup that does not depend on screen visibility. Here you configure the collectionView, register nib files for cells, create data sources and layouts. If you are loading data from the network, in viewDidLoad it is better to only initiate the request, and update the UI in viewWillAppear when the screen is ready for display.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(
MyCell.self,
forCellReuseIdentifier: MyCell.identifier
)
viewModel.loadInitialData()
}
Use viewWillAppear for data synchronization every time the screen appears. For example, if the user could have changed settings on the previous screen, here you update the displayed values, reload the table, and adjust the NavigationBar state. This guarantees that the screen always shows up-to-date data in any navigation scenario.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
navigationController?.setNavigationBarHidden(false, animated: animated)
}
viewDidAppear is ideal for starting animations that should begin after the user has seen the screen. Here you also send analytics events: screen view, onboarding start, or video playback initiation. Starting animations before the transition is complete leads to janky interface — UIKit does not have enough time to prepare a sufficient number of frames.
In viewWillDisappear, you save drafts, stop timers, and unsubscribe from NotificationCenter. This is the last moment when the screen is still visible and accessible for operations requiring user context. For critical data, additionally use viewDidDisappear as a safeguard against cancelled gestures.
Incorrect usage of lifecycle methods is one of the most frequent sources of bugs in iOS apps. Let’s look at the main mistakes developers make at different stages of working with UIViewController.
The first mistake — creating subviews in init or loadView when using Storyboard. If you are using Interface Builder, do not override loadView unnecessarily. Creating a View in loadView when a storyboard exists results in ignoring the xib file and an empty screen.
The second mistake — subscribing to keyboard notifications in viewDidLoad without unsubscribing. If you subscribed to UIResponder.keyboardWillShowNotification but did not unsubscribe when the screen is hidden, the block will be called even after the controller’s deinit — this is a memory leak with potential app crashes.
The third mistake — timers and network requests started before the screen appears. Loading images or performing animations when the View is not yet visible is a waste of resources. Move visual updates to viewWillAppear or viewDidAppear.
The fourth mistake — saving data only in viewWillDisappear. With an interactive pop gesture, the user may start a swipe and cancel it — the method was called but the screen did not disappear. Duplicate critical saving in viewDidDisappear or in the applicationDidEnterBackground handler.
Frequently Asked Questions
Once — after the View is loaded into memory. When the screen appears again, viewDidLoad is not called. If you need to recreate the View, the controller must be destroyed and created anew.
UIKit requires calling super.viewDidLoad for the lifecycle to work correctly. Without it, issues with layout updates and transition handling may occur. Always call super first thing inside the method.
Not recommended. If the controller is initialized from Storyboard, UIKit automatically loads the View from the xib. Overriding loadView cancels this process, and your storyboard will be ignored.
Subscribe in viewDidLoad or viewWillAppear, and unsubscribe in viewWillDisappear or viewDidDisappear, using a weak reference to self to avoid memory leaks with closures.
Force quit kills the process abruptly — UIKit does not have time to call Lifecycle methods. To save data, use the UIApplication.willTerminateNotification in AppDelegate.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also