App States & Transitions
The Five App States
iOS apps transition through five states managed by the system:
1. Not Running - The app has not been launched or was terminated. No code is executing.
2. Inactive - The app is in the foreground but not receiving events. This is a transient state lasting seconds during transitions like app switching or incoming calls.
3. Active - The app is in the foreground and receiving events. This is the normal running state.
4. Background - The app is executing code but not visible. The system may terminate it after a few seconds unless background execution is requested.
5. Suspended - The app is in memory but not executing code. The system can terminate suspended apps at any time.
State Transitions
Not Running -> (launch) -> Inactive -> (foreground) -> Active
Active -> (home button) -> Inactive -> Background -> Suspended -> Not Running
Background -> (background task) -> Inactive -> Active
Monitoring with scenePhase
SwiftUI provides the scenePhase environment value to observe lifecycle transitions:
@main
struct MyApp: App {
@Environment(\.scenePhase) var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { _, phase in
switch phase {
case .active:
print("App is active")
case .inactive:
print("App is inactive")
case .background:
print("App is in background")
@unknown default:
break
}
}
}
}
Practical Use Cases
Save state when entering background:
.onChange(of: scenePhase) { _, phase in
if phase == .background {
saveUserDefaults()
saveDraftContent()
}
}
Refresh data when returning to active:
.onChange(of: scenePhase) { _, phase in
if phase == .active {
Task { await refreshData() }
}
}
Background Execution
When the app enters the background, it has approximately 3-5 seconds to finish critical work. For longer tasks, request background execution time:
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
backgroundTask = UIApplication.shared.beginBackgroundTask {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
Task {
await uploadPendingChanges()
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
State Restoration
SwiftUI automatically preserves and restores WindowGroup state. The system may terminate suspended apps to reclaim memory, so saving user progress in the background callback is essential.
@main Entry Point
The @main Attribute
The @main attribute marks the entry point of your app. In SwiftUI, this is an App struct:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
The App struct conforms to the App protocol, requiring a body property returning some Scene. SwiftUI handles the UIKit connection automatically.
Custom Initialization
@main
struct MyApp: App {
init() {
setupAnalytics()
configureAppearance()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
UIApplicationDelegateAdaptor
For lower-level lifecycle events, use UIApplicationDelegateAdaptor:
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
setupPushNotifications()
setupAppearance()
return true
}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Handle push notification token
}
}
App vs Scene Level
- App level: Single-instance concerns (push notifications, analytics, deep linking)
- Scene level: Per-window concerns (navigation state, UI configuration)
This distinction matters for multi-window iPad apps where each window may have different state.
Scene Configuration
What Are Scenes?
A Scene represents an instance of your app's UI. On iPhone, typically one scene. On iPad, multiple scenes are supported.
var body: some Scene {
WindowGroup {
ContentView()
}
}
WindowGroup
WindowGroup provides automatic state preservation, multi-window support, and lifecycle management:
WindowGroup("Document Editor") {
DocumentView()
}
Settings Scene
var body: some Scene {
WindowGroup {
ContentView()
}
Settings {
SettingsView()
}
}
Background Tasks
Register for background tasks at app launch:
func registerBackgroundTasks() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { task in
handleRefresh(task: task as! BGAppRefreshTask)
}
}
func scheduleRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
try? BGTaskScheduler.shared.submit(request)
}
Multiple Windows on iPad
@main
struct MyApp: App {
var body: some Scene {
WindowGroup("Main") {
ContentView()
}
WindowGroup("Settings") {
SettingsView()
}
.handlesExternalEvents(matching: ["settings"])
}
}
The .handlesExternalEvents(matching:) modifier specifies which URL events activate each window group.
Quiz
1. What are the five app lifecycle states?
2. How do you observe lifecycle state changes in SwiftUI?
3. What does @UIApplicationDelegateAdaptor provide?
4. How long does an app typically have to finish work after entering the background?
5. What is the difference between App-level and Scene-level concerns?
Flashcards
Question
What are the five app lifecycle states?
Click to reveal answer
Answer
Not Running, Inactive, Active, Background, Suspended.
Question
How do you observe lifecycle changes in SwiftUI?
Click to reveal answer
Answer
@Environment(\.scenePhase) combined with .onChange() modifier.
Question
What does @main mark?
Click to reveal answer
Answer
The entry point of the app, typically a struct conforming to the App protocol.
Question
What is background task execution time?
Click to reveal answer
Answer
Approximately 3-5 seconds after entering background before the app is suspended.
Question
What bridges UIKit AppDelegate into SwiftUI?
Click to reveal answer
Answer
@UIApplicationDelegateAdaptor, which wraps a UIApplicationDelegate class.
Revision Notes
Key Takeaways
- 1. iOS apps have five lifecycle states: Not Running, Inactive, Active, Background, Suspended
- 2. scenePhase environment value monitors lifecycle changes in SwiftUI
- 3. @main marks the App struct as the entry point
- 4. UIApplicationDelegateAdaptor bridges UIKit AppDelegate methods
- 5. Save state before background; apps have ~3-5 seconds
Interview Tips
- • List all five app lifecycle states and their transitions
- • Explain how scenePhase works with .onChange() for lifecycle monitoring
- • Discuss when to use UIApplicationDelegateAdaptor vs pure SwiftUI
- • Know the background execution time limit and how to handle it
Cheat Sheet
Five states: Not Running, Inactive, Active, Background, Suspended.
@Environment(.scenePhase) monitors lifecycle in SwiftUI.
@main marks the app entry point.
@UIApplicationDelegateAdaptor bridges UIKit lifecycle.
Background has ~3-5 seconds for cleanup.