Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
187: UIViewController Lifecycle Revisited
I remember a few years ago I was reviewing a PR for a junior developer who was building a custom profile header. He was absolutely pulling his hair out because his center-aligned profile image kept jumping to the left side of the screen every time the app launched, despite his math being "perfect." He had written the layout logic inside viewDidLoad. He was calculating the center of the screen based on self.view.frame.width, but in viewDidLoad, the view hasn't actually been placed in the window hierarchy yet. He was performing calculations against a default storyboard size or a zero-width frame, and the layout only "fixed" itself once the system performed its own layout pass. It was a classic case of trusting the wrong lifecycle hook.
The Gap Between Loading and Layout
We all lean on viewDidLoad because it's the most intuitive place to put setup code. It runs once, it's predictable, and it's where we usually initialize our data. But you have to remember that "loaded" doesn't mean "laid out." When viewDidLoad fires, the view exists in memory, but its final dimensions aren't guaranteed. If you're trying to calculate a frame, set a corner radius based on a view's height, or position a subview precisely, viewDidLoad is the wrong place.
That's where viewDidLayoutSubviews comes in. This method is called after the view has adjusted the frames of its subviews. If you need to do something that depends on the actual size of the view on the user's specific device—like an iPhone SE versus a 15 Pro Max—this is where that logic belongs. Just be careful: this method can be called multiple times (for example, during a screen rotation). If you put a heavy API call or a complex database query here, you're going to tank your frame rate.
Managing the Appearance Cycle
Then we have the "appearance" methods: viewWillAppear and viewDidAppear. I often see developers treat these as interchangeable, but they serve very different purposes. viewWillAppear is your last chance to make changes before the user actually sees the screen. It's the perfect spot to hide the navigation bar or refresh a small piece of state that might have changed while the user was on a different screen.
viewDidAppear, on the other hand, is the place for things that must happen after the view is visible. Think of animations, starting a video player, or presenting an alert. If you try to present a modal or trigger a "Welcome" animation in viewWillAppear, iOS will often ignore it or throw a warning in the console because the view hierarchy isn't fully "settled" yet. I've spent way too many hours debugging "invisible" alerts that were simply called too early in the lifecycle.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Update the UI to reflect data changes from other screens
self.updateUserStatus()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Trigger a smooth fade-in animation for the profile picture
self.animateProfileEntrance()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Now we have the actual width, so we can make the image a perfect circle
profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
}
One last thing: never forget to call super. It seems like a formality, but the underlying UIViewController class does a lot of housekeeping in these methods. If you omit super.viewWillAppear(animated), you might find that your view doesn't behave correctly with the navigation controller or that certain system events just stop firing. It's a small line of code that saves you from some very strange, hard-to-track bugs.
📋 Practical Task
Exercise: The Frame-Aware Profile Header
Build a simple UIViewController that demonstrates the difference between the loading and layout phases. Follow these requirements:
- Add a
UIView(the "Header") to the center of the screen with a background color. - Create a
UILabelthat displays the current width of the Header view. - In
viewDidLoad, update the label to show the Header's width. - In
viewDidLayoutSubviews, update the label again to show the Header's width. - Run the app and observe the label. You should see the width change (or start as a default value and then snap to the correct value) as the lifecycle progresses.
- As a bonus, add a
printstatement toviewWillAppearandviewDidAppearto verify the order in which they execute in the console.
There are no comments for now.