Lets go through the View Controller lifecycle in Swift, yes it is definitely different in SwiftUI.
1. loadView()
In iOS UI can be generated in 3 ways, using storyboard, using xib or using code only. If we are creating UI using code
, loadView() is responsible for creating and assigning the main view of your view controller.
You override loadView()
when you want to create the entire view hierarchy manually in code, without using a storyboard or .xib
.
override func loadView() {
let customView = UIView()
customView.backgroundColor = .white
let label = UILabel()
label.text = "Hello, Swift!"
label.textAlignment = .center
label.frame = CGRect(x: 50, y: 100, width: 200, height: 40)
customView.addSubview(label)
self.view = customView // Must assign a view to self.view
}
⚠️ Important: If you override loadView()
, you must assign something to self.view
. If not, the app will crash.
2. viewDidLoad()
This method is called once in view controller’s life cycle, after the view controller’s view is loaded into memory, but before it appears on screen. It’s where you do one-time setup work like initializing values, setting up UI, and configuring the view.
override func viewDidLoad() {
super.viewDidLoad()
// Set background color
view.backgroundColor = .white
// Configure label
welcomeLabel.text = "Welcome to the app!"
welcomeLabel.textColor = .darkGray
// Set delegate for table view
tableView.delegate = self
tableView.dataSource = self
// Register custom cell
tableView.register(UINib(nibName: "MyCustomCell", bundle: nil), forCellReuseIdentifier: "cell")
}
3. viewWillAppear()
This method is called every time the view is about to appear, even after coming back from another screen. It’s ideal for tasks that need to refresh each time the view becomes visible.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Update welcome label with latest username
welcomeLabel.text = "Welcome, \(UserDefaults.standard.string(forKey: "username") ?? "Guest")"
// Refresh table data
tableView.reloadData()
// Show navigation bar if previously hidden
navigationController?.setNavigationBarHidden(false, animated: true)
}
4. viewWillLayoutSubviews()
This method is a lifecycle method in UIViewController
that is called just before the layout engine lays out your subviews.
In short: It’s triggered before the system calculates frames and sizes of subviews.
When Is It Called?
- Every time the view’s bounds change (like after rotation or resizing)
- After
viewDidLoad()
and beforeviewDidAppear()
- Whenever
setNeedsLayout()
orlayoutIfNeeded()
is called
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Adjust a view's frame dynamically
myButton.frame = CGRect(
x: 20,
y: view.bounds.height - 100,
width: view.bounds.width - 40,
height: 50
)
}
This is helpful when your layout depends on actual view size, which isn’t finalized yet in viewDidLoad
.
5. viewDidLayoutSubviews()
This method is a UIKit UIViewController
method called after the system finishes laying out the view controller’s subviews.
In simple words: It’s your chance to react to the final layout of the views — their sizes and positions have been set. Once layout is done, you can animate based on final positions.
6. viewDidAppear()
This method is a UIViewController
lifecycle method that is called after the view has fully appeared on screen and is visible to the user.
It’s the first place in the lifecycle where you can safely interact with things that need the view to be 100% visible. Here you can start animations, sending analytics, beginning of timers, etc.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Send analytics event
Analytics.logEvent("home_screen_viewed", parameters: nil)
// Start a loading animation
loadingSpinner.startAnimating()
// Show welcome alert
if isFirstLaunch {
showWelcomeAlert()
}
}
7. viewWillDisappear()
This method is called just before the view controller’s view is removed from the screen — either because:
- You’re navigating away (e.g., pushing a new view controller)
- The view is being dismissed
Think of it as your “clean up or pause” moment before the view leaves. Here you can pause timers, remove observers, stop loading spinners, etc.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Stop the timer
refreshTimer.invalidate()
// Pause video playback
videoPlayer.pause()
// Save draft data
saveFormState()
// Log analytics
Analytics.logEvent("profile_view_closed", parameters: nil)
}
8. viewDidDisappear()
is a UIKit UIViewController
lifecycle method that’s called after the view has fully disappeared from the screen.
It’s the moment when the view is completely gone, and you can safely release or stop things that you no longer need. Here you can cancel network calls, stop timers, or animations, clean up resources, etc.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Cancel any webservice tasks
loadingTask?.cancel()
// Stop the music if it's playing
musicPlayer.stop()
// Save progress
saveUserProgress()
// Log for analytics
Analytics.logEvent("checkout_screen_closed", parameters: nil)
}
9. deinit()
deinit
is a deinitializer — a special method in Swift that’s called just before an instance of a class is deallocated from memory.
Think of it as your “cleanup before death” function — used to release resources, remove observers, or close files.
When Does deinit
Get Called?
- Automatically by the system when the reference count of a class instance reaches zero
- It’s only available in classes (not structs or enums)
- You never call
deinit
directly — Swift does it for you - If
deinit
isn’t being called, it’s likely because: something is strongly retaining the object (e.g., a closure, delegate, or view hierarchy)
10. didReceiveMemoryWarning()
This method in UIViewController
that gets called when the system is running low on memory.
It’s your chance to free up memory by releasing anything that can be recreated later (like cached images or data).
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Clear in-memory image cache
imageCache.removeAllObjects()
// Nullify large data that can be fetched again
largeJSONData = nil
print("Memory warning received. Cleaned up resources.")
}
11. viewWillTransition(to:with:)
This method is called whenever the view controller’s size is about to change, such as:
- Device rotation (portrait ↔ landscape)
- Multitasking size changes on iPad
- Any layout changes that affect
view.bounds.size
It gives you a chance to adjust layout or animations before the size actually changes.
When to Use:
- When you want custom animations during rotation
- When manual layout is being used
- When you want to log orientation or size changes cleanly
Conclusion: Understanding UIKit Lifecycles
1. View Lifecycle (Manages View Appearance)
Method | Purpose |
---|---|
viewDidLoad() | One-time setup when view loads |
viewWillAppear() | Prepare UI just before view appears |
viewDidAppear() | Start animations, tracking after visible |
viewWillDisappear() | Pause tasks before view disappears |
viewDidDisappear() | Final cleanup after view is hidden |
2. Layout Lifecycle (Handles Size & Orientation Changes)
Method | Purpose |
---|---|
viewWillLayoutSubviews() | Adjust layout before subviews are laid out |
viewDidLayoutSubviews() | Make changes after layout is complete |
viewWillTransition(to:with:) | Handle size/rotation changes (e.g., rotate) |
3. Memory Lifecycle (Manages Resources)
Method | Purpose |
---|---|
didReceiveMemoryWarning() | Free up memory during system pressure |
deinit | Final cleanup when object is deallocated |
Thank you!