viewDidLoad is the first method UIKit calls after loading the UIViewController’s View into memory. According to Apple Developer Documentation, this method is called exactly once during the entire lifetime of the controller. viewDidLoad is the primary place for initial interface setup, cell registration, and data initialization.
Key Takeaways
viewDidLoad is an instance method of UIViewController that UIKit calls immediately after the controller’s View has been loaded into memory. By this point, all IBOutlet properties are already connected to interface elements, but the View has not yet been added to the window hierarchy and is not visible to the user. The developer overrides this method to perform the initial screen configuration.
The method is part of the ViewController Lifecycle and follows immediately after loadView if the View is created programmatically, or after loading from Storyboard. In a typical project, viewDidLoad is the most frequently overridden method of UIViewController, as it provides a safe point for working with subviews that already exist and are ready for configuration.
An important detail: by the time viewDidLoad is called, the View’s dimensions do not yet correspond to the final ones — Auto Layout has not completed its passes, and the frame may differ from expectations. For calculations that depend on dimensions, use viewDidLayoutSubviews.
The timing of the viewDidLoad call depends on how the controller is initialized. In most cases, UIKit calls this method automatically the first time the controller’s view property is accessed — this is called the lazy-loading mechanism of UIViewController.
When a NavigationController or TabBarController first displays your screen, UIKit checks whether the View is loaded. If not — loadView is called (or loading from Storyboard), after which viewDidLoad is immediately triggered. This is the standard scenario, and it happens once for each controller instance.
override func viewDidLoad() {
super.viewDidLoad()
print("View loaded — you can configure the interface")
setupUI()
configureTableView()
}
viewDidLoad is not called again when returning to the screen via the back button or dismiss. If your logic depends on the screen appearing again — place it in viewWillAppear. This is one of the most common conceptual mistakes: developers expect viewDidLoad to fire on every display, but UIKit calls it only once.
Sometimes developers forcibly access the controller’s view to trigger loading in advance: let _ = controller.view. This forces loadView and viewDidLoad to be called before the controller appears on screen. This technique is used when you need to prepare the View in advance for a smooth transition.
viewDidLoad is intended for one-time setup operations that do not depend on whether the screen is visible. Proper use of this method is the key to clean architecture and predictable controller behavior.
In viewDidLoad, you register nib files and classes for UITableView and UICollectionView, configure delegates, and set initial values for UI element properties. Since all IBOutlets are already connected by this point, you can safely access label.text, imageView.image, and other subview properties.
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(
CustomCell.self,
forCellReuseIdentifier: CustomCell.identifier
)
title = "Main Screen"
}
Here you create a viewModel, initialize data source with arrays, and subscribe to notifications that should be active throughout the controller’s lifetime. For example, subscribing to UIApplication.willEnterForegroundNotification to update data when returning from the background is a good candidate for viewDidLoad. The viewModel in modern iOS architecture acts as a bridge between the controller and business logic, and initializing it in viewDidLoad ensures data readiness by the time the screen first appears.
Pay special attention to configuring the data source for tables and collections. If your table uses UIFetchedResultsController or NSFetchedResultsController with Core Data, initialize the fetch request and delegate in viewDidLoad. This guarantees that when the screen first appears, the table will already be populated with data without additional requests.
In viewDidLoad, you configure NavigationBar buttons, set up large title, add a search controller, and set edit/done buttons. These elements rarely change when the screen is shown again, so initializing them here is optimal.
Not all operations are appropriate in viewDidLoad. Some actions placed in this method lead to excessive memory consumption, incorrect behavior, or bugs when the screen is shown again.
Avoid starting network requests whose result only affects the UI. If the request completes before the screen appears, the user won’t see the result, and if it completes after — the data may be outdated. Initiate loading in viewDidLoad, but update the UI in viewWillAppear.
Do not perform operations in viewDidLoad that depend on the View’s size and position. At the time of the call, Auto Layout has not completed its passes, and the frame may not be final. For calculations, use viewDidLayoutSubviews or override updateViewConstraints.
Do not subscribe to notifications that are only relevant when the screen is visible. Keyboard notifications, content change notifications from child controllers — subscribe to them in viewWillAppear and unsubscribe in viewDidDisappear to avoid unnecessary calls and leaks.
Do not call methods that require a visible screen. For example, attempting to show a UIAlertController from viewDidLoad will result in an error because the controller’s View has not yet been added to the window hierarchy. Any UI operations that depend on the window or presentedViewController should only be performed after the screen appears.
Do not initialize heavy resources unnecessarily. If the screen is rarely shown or data is not displayed immediately, defer the creation of resource-intensive objects until they are actually needed. Lazy property initialization in Swift is a built-in mechanism for solving this problem: a property with the lazy modifier will be created only on first access, saving memory and speeding up screen loading.
Do not use viewDidLoad for operations that should be performed every time the screen appears. This is the most fundamental mistake: beginner developers often place data update logic in viewDidLoad and are surprised that when returning from another screen, the table does not reload. If an operation should repeat on every display — use viewWillAppear. If it should execute once per lifetime — use viewDidLoad. Remember this simple rule to avoid most problems with the UIViewController lifecycle.
Let’s look at three practical examples demonstrating the correct use of viewDidLoad in real projects. Each example solves a specific screen configuration task.
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(
PhotoCell.self,
forCellWithReuseIdentifier: PhotoCell.reuseId
)
collectionView.register(
HeaderView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: HeaderView.reuseId
)
viewModel.delegate = self
viewModel.fetchInitialPage()
}
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Hello, world!"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
In viewDidLoad, you also configure elements displayed when there is no data: empty state, loader, placeholder. These components are created once and reused each time the screen appears. Hiding or showing these elements is managed in viewWillAppear depending on the current data.
override func viewDidLoad() {
super.viewDidLoad()
emptyStateLabel = UILabel()
emptyStateLabel.text = "No data"
emptyStateLabel.textAlignment = .center
emptyStateLabel.isHidden = true
view.addSubview(emptyStateLabel)
activityIndicator = UIActivityIndicatorView(style: .medium)
activityIndicator.hidesWhenStopped = true
view.addSubview(activityIndicator)
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(handleEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil
)
}
@objc private func handleEnterForeground() {
refreshContent()
}
Frequently Asked Questions
Under normal conditions, no — UIKit calls viewDidLoad once after loading the View into memory. If the controller is destroyed and created again, viewDidLoad will fire for the new instance.
Yes, absolutely. Calling super.viewDidLoad ensures that UIKit performs the internal setup necessary for the Lifecycle to work correctly. Always call super first thing inside the method.
viewDidLoad is called once when the View loads. viewWillAppear is called every time before the screen appears. The first is for one-time setup, the second is for updating data and state.
Heavy synchronous operations in viewDidLoad block the main thread and delay the screen appearance. Asynchronous loading is acceptable, but updating the UI upon completion must account for the possibility that the screen may already be hidden.
You cannot call viewDidLoad directly — UIKit calls it. To force the View to load, access the controller.view property. This will trigger loadView and viewDidLoad automatically.
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