Skip to content
beginner Phase 3 · App Lifecycle & Architecture

App Lifecycle

Understand app states, transitions, and the role of AppDelegate and SceneDelegate.

40m
0 problems
Topic Progress 0%

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?

Question 1 options

2. How do you observe lifecycle state changes in SwiftUI?

Question 2 options

3. What does @UIApplicationDelegateAdaptor provide?

Question 3 options

4. How long does an app typically have to finish work after entering the background?

Question 4 options

5. What is the difference between App-level and Scene-level concerns?

Question 5 options

Flashcards

Question

What are the five app lifecycle states?

Answer

Not Running, Inactive, Active, Background, Suspended.

Question

How do you observe lifecycle changes in SwiftUI?

Answer

@Environment(\.scenePhase) combined with .onChange() modifier.

Question

What does @main mark?

Answer

The entry point of the app, typically a struct conforming to the App protocol.

Question

What is background task execution time?

Answer

Approximately 3-5 seconds after entering background before the app is suspended.

Question

What bridges UIKit AppDelegate into SwiftUI?

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.