Migrating AppDelegate to SceneDelegate
Aug 3, 2025
This document provides an in-depth exploration of AppDelegate
and SceneDelegate
in iOS applications, their respective functions, a comparison table mapping AppDelegate
functions to their SceneDelegate
equivalents, and a detailed guide on migrating from AppDelegate
to SceneDelegate
for apps adopting the Scene-based architecture introduced in iOS 13.
1. AppDelegate
Overview
The AppDelegate
class is a core component of an iOS application, serving as the entry point for the app and managing its lifecycle. It is responsible for handling app-level events, such as launching, terminating, and responding to system notifications. The AppDelegate
is typically implemented in a class conforming to the UIApplicationDelegate
protocol.
Key Functions of AppDelegate
The AppDelegate
handles application-wide events and configurations. Below are the primary methods defined in the UIApplicationDelegate
protocol, organized by their purpose:
Application Lifecycle Methods
application(_:didFinishLaunchingWithOptions:)
- Purpose: Called when the app has finished launching.
- Usage: Initialize app-wide resources, configure the initial UI, and perform setup tasks (e.g., setting up third-party libraries, restoring app state, or configuring the root view controller).
- Example:swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = UINavigationController(rootViewController: ViewController()) window?.makeKeyAndVisible() return true }
- Notes: This is often the first method called after the app starts. Return
true
to indicate successful initialization.
applicationDidBecomeActive(_:)
- Purpose: Called when the app becomes the active app (e.g., after launching or resuming from the background).
- Usage: Refresh UI, resume tasks, or start animations.
- Notes: This method is called after the app transitions to the foreground and is fully active.
applicationWillResignActive(_:)
- Purpose: Called when the app is about to move from the active to inactive state (e.g., due to an incoming call or user switching apps).
- Usage: Pause ongoing tasks, disable timers, or save temporary state.
- Notes: Avoid heavy processing, as the app may transition quickly to the background.
applicationDidEnterBackground(_:)
- Purpose: Called when the app enters the background.
- Usage: Save user data, release shared resources, and store app state for restoration.
- Notes: The app has limited time to execute tasks in this method, so use background tasks if needed.
applicationWillEnterForeground(_:)
- Purpose: Called when the app is about to enter the foreground from the background.
- Usage: Undo changes made in
applicationDidEnterBackground(_:)
, such as restarting paused tasks. - Notes: Prepare the app to resume active operation.
applicationWillTerminate(_:)
- Purpose: Called when the app is about to terminate.
- Usage: Perform final cleanup, save critical data, and release resources.
- Notes: Termination may not always occur (e.g., if the app is suspended), so save state proactively.
Other Important Methods
application(_:open:options:)
- Purpose: Handles opening URLs (e.g., deep links or universal links).
- Usage: Process incoming URLs to navigate to specific content or handle custom URL schemes.
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
- Purpose: Handles remote push notifications.
- Usage: Process notification data and update the app’s state.
application(_:configurationForConnecting:options:)
- Purpose: Introduced in iOS 13 to support SceneDelegate; provides a scene configuration for a new scene session.
- Usage: Specify which scene configuration to use for a new session.
application(_:didDiscardSceneSessions:)
- Purpose: Called when the user discards scene sessions (e.g., closing app windows in a multi-window app).
- Usage: Clean up resources associated with discarded scenes.
Role in Pre-iOS 13 Apps
Before iOS 13, AppDelegate
was responsible for managing both the app’s lifecycle and the UI lifecycle (e.g., setting up the UIWindow
and root view controller). It acted as the central coordinator for the entire application, handling all lifecycle events for the app and its single window.
2. SceneDelegate
Overview
Introduced in iOS 13, the SceneDelegate
class is part of the Scene-based architecture, which allows iOS apps to support multiple windows (scenes) on devices like iPads and iPhones with iOS 13 or later. The SceneDelegate
manages the lifecycle of individual scenes (UI instances) within the app, offloading UI-related responsibilities from the AppDelegate
.
Key Functions of SceneDelegate
The SceneDelegate
conforms to the UISceneDelegate
protocol and handles scene-specific lifecycle events. Below are the primary methods:
Scene Lifecycle Methods
scene(_:willConnectTo:options:)
- Purpose: Called when a new scene is created and connected to the app.
- Usage: Set up the scene’s
UIWindow
, configure the root view controller, and initialize scene-specific resources. - Example:swift
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) window?.rootViewController = UINavigationController(rootViewController: ViewController()) window?.makeKeyAndVisible() }
- Notes: This is the SceneDelegate equivalent of
application(_:didFinishLaunchingWithOptions:)
for UI setup.
sceneDidDisconnect(_:)
- Purpose: Called when a scene is disconnected (e.g., when the user closes a window or the system terminates it).
- Usage: Clean up scene-specific resources and save state.
- Notes: The scene session may be discarded or reused later.
sceneDidBecomeActive(_:)
- Purpose: Called when the scene becomes active (e.g., when the user interacts with the window).
- Usage: Resume scene-specific tasks, refresh UI, or start animations.
sceneWillResignActive(_:)
- Purpose: Called when the scene is about to become inactive.
- Usage: Pause scene-specific tasks or save temporary state.
sceneWillEnterForeground(_:)
- Purpose: Called when the scene is about to enter the foreground.
- Usage: Prepare the scene to become active again, such as restoring UI state.
sceneDidEnterBackground(_:)
- Purpose: Called when the scene enters the background.
- Usage: Save scene-specific data and release resources.
Other Important Methods
scene(_:openURLContexts:)
- Purpose: Handles URL opening for a specific scene.
- Usage: Process deep links or universal links for the scene’s context.
scene(_:continue:)
- Purpose: Handles continuing a user activity (e.g., Handoff or Spotlight actions).
- Usage: Restore the scene to a specific state based on the activity.
Role in iOS 13 and Later
The SceneDelegate
manages the lifecycle of individual scenes, which represent separate instances of the app’s UI. This enables features like multi-window support on iPadOS and improved state restoration. Each scene has its own UIWindow
and lifecycle, independent of other scenes in the same app.
3. Comparison of AppDelegate and SceneDelegate Functions
The following table maps AppDelegate
methods to their corresponding SceneDelegate
methods, highlighting their roles and differences:
AppDelegate Method | SceneDelegate Method | Purpose | Notes |
---|---|---|---|
application(_:didFinishLaunchingWithOptions:) | scene(_:willConnectTo:options:) | Initialize the app or scene, set up the UI (e.g., UIWindow , root view controller). | In AppDelegate , this handles both app and UI setup (pre-iOS 13). In SceneDelegate , it focuses on scene-specific UI setup. |
applicationDidBecomeActive(_:) | sceneDidBecomeActive(_:) | Handle the app/scene becoming active. | AppDelegate manages app-wide activation; SceneDelegate manages scene-specific activation (e.g., a specific window). |
applicationWillResignActive(_:) | sceneWillResignActive(_:) | Handle the app/scene transitioning to an inactive state. | Similar roles, but SceneDelegate applies to individual scenes, allowing per-window state management. |
applicationDidEnterBackground(_:) | sceneDidEnterBackground(_:) | Handle the app/scene entering the background. | AppDelegate saves app-wide state; SceneDelegate saves scene-specific state. |
applicationWillEnterForeground(_:) | sceneWillEnterForeground(_:) | Prepare the app/scene to enter the foreground. | SceneDelegate allows restoring UI state for individual scenes. |
applicationWillTerminate(_:) | sceneDidDisconnect(_:) | Clean up before app/scene termination. | applicationWillTerminate(_:) is app-wide and called only when the app fully terminates; sceneDidDisconnect(_:) is called when a scene is closed (e.g., in multi-window apps). |
application(_:open:options:) | scene(_:openURLContexts:) | Handle URL opening (e.g., deep links). | AppDelegate handles app-wide URL processing; SceneDelegate handles URLs for a specific scene. |
application(_:didReceiveRemoteNotification:fetchCompletionHandler:) | No direct equivalent | Handle push notifications. | Push notifications remain app-wide, typically handled in AppDelegate . Scene-specific notifications may be processed in SceneDelegate if relevant to a specific window. |
application(_:continue:restoreHandler:) | scene(_:continue:) | Handle user activities (e.g., Handoff, Spotlight). | AppDelegate handles app-wide activities; SceneDelegate restores state for a specific scene. |
application(_:configurationForConnecting:options:) | N/A | Provide scene configuration for new sessions. | Exclusive to AppDelegate in iOS 13+ to support SceneDelegate . |
application(_:didDiscardSceneSessions:) | N/A | Clean up discarded scene sessions. | Exclusive to AppDelegate for managing scene session lifecycle. |
Key Observations
- UI Management: In pre-iOS 13 apps,
AppDelegate
handles both app and UI lifecycle events. In iOS 13+, UI-related tasks (e.g.,UIWindow
setup) move toSceneDelegate
. - Granularity:
SceneDelegate
methods are scene-specific, enabling multi-window support, whileAppDelegate
methods are app-wide. - New Responsibilities:
AppDelegate
gains scene-related methods (configurationForConnecting
,didDiscardSceneSessions
) to support the Scene-based architecture.
4. Migrating from AppDelegate to SceneDelegate
Why Migrate?
Starting with iOS 13, Apple introduced the Scene-based architecture to support multiple windows and improve app lifecycle management. Migrating to SceneDelegate
is necessary to take advantage of modern iOS features, such as:
- Multi-window support on iPadOS.
- Improved state restoration for individual windows.
- Better separation of concerns between app-wide and UI-specific logic.
Migration Steps
Migrating an app from using only AppDelegate
to supporting SceneDelegate
involves restructuring the app to handle scene-based lifecycle events. Below is a step-by-step guide:
Step 1: Update the Project to Support Scenes
Check Deployment Target:
- Ensure the app’s deployment target is iOS 13.0 or later, as
SceneDelegate
is not available in earlier versions. - If the app must support iOS 12 or earlier, implement conditional logic to handle both
AppDelegate
andSceneDelegate
.
- Ensure the app’s deployment target is iOS 13.0 or later, as
Add Scene Support to Info.plist:
- Open the app’s
Info.plist
file and add theUIApplicationSceneManifest
key to enable scene support. - Example configuration:xml
<key>UIApplicationSceneManifest</key> <dict> <key>UIApplicationSupportsMultipleScenes</key> <true/> <key>UISceneConfigurations</key> <dict> <key>UIWindowSceneSessionRoleApplication</key> <array> <dict> <key>UISceneConfigurationName</key> <string>Default Configuration</string> <key>UISceneDelegateClassName</key> <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string> <key>UISceneStoryboardFile</key> <string>Main</string> </dict> </array> </dict> </dict>
- Explanation:
UIApplicationSupportsMultipleScenes
: Set totrue
to enable multi-window support (optional).UISceneConfigurationName
: A unique name for the scene configuration.UISceneDelegateClassName
: Specifies theSceneDelegate
class.UISceneStoryboardFile
: (Optional) Specifies the storyboard for the scene.
- Open the app’s
Remove UIWindow from AppDelegate:
- If your
AppDelegate
creates aUIWindow
, remove this code, as theSceneDelegate
will now manage the window.
- If your
Step 2: Create a SceneDelegate Class
Add a SceneDelegate File:
- In Xcode, create a new Swift file named
SceneDelegate.swift
. - Implement a class that conforms to
UISceneDelegate
:swiftimport UIKit class SceneDelegate: UIResponder, UISceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) window?.rootViewController = UINavigationController(rootViewController: ViewController()) window?.makeKeyAndVisible() } func sceneDidDisconnect(_ scene: UIScene) { } func sceneDidBecomeActive(_ scene: UIScene) { } func sceneWillResignActive(_ scene: UIScene) { } func sceneWillEnterForeground(_ scene: UIScene) { } func sceneDidEnterBackground(_: UIScene) { } }
- Notes: Move the UI setup code (e.g., creating the
UIWindow
and setting the root view controller) fromapplication(_:didFinishLaunchingWithOptions:)
inAppDelegate
toscene(_:willConnectTo:options:)
inSceneDelegate
.
- In Xcode, create a new Swift file named
Update AppDelegate:
- Modify
AppDelegate
to support scene-based lifecycle by implementing scene configuration methods:swiftimport UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Perform app-wide initialization (e.g., configure third-party libraries) return true } func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { let configuration = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) configuration.delegateClass = SceneDelegate.self return configuration } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { // Clean up discarded scenes } }
- Notes: The
configurationForConnecting
method specifies the scene configuration and delegate class.
- Modify
Step 3: Refactor Lifecycle Management
Move UI-Related Code:
- Relocate UI-related code (e.g., window setup, root view controller configuration) from
AppDelegate
toSceneDelegate
. - Example: Move
window
setup fromapplication(_:didFinishLaunchingWithOptions:)
toscene(_:willConnectTo:options:)
.
- Relocate UI-related code (e.g., window setup, root view controller configuration) from
Handle Scene-Specific Lifecycle:
- Implement scene lifecycle methods in
SceneDelegate
to manage UI state (e.g., saving/restoring UI state, refreshing views). - Example: Save scene-specific state in
sceneDidEnterBackground(_:)
and restore it insceneWillEnterForeground(_:)
.
- Implement scene lifecycle methods in
Support Multiple Scenes:
- If enabling multi-window support, ensure each scene has its own
UIWindow
and state management. - Handle scene creation and destruction appropriately in
SceneDelegate
methods.
- If enabling multi-window support, ensure each scene has its own
Step 4: Support Older iOS Versions
If the app supports iOS 12 or earlier, implement conditional logic to handle both AppDelegate
and SceneDelegate
:
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13.0, *) {
// SceneDelegate handles UI setup
return true
} else {
// Fallback for iOS 12 and earlier
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UINavigationController(rootViewController: ViewController())
window?.makeKeyAndVisible()
return true
}
}
}
- Notes: Use the
#available
check to ensure compatibility with older iOS versions.
Step 5: Test the Migration
Test Lifecycle Events:
- Verify that
SceneDelegate
methods are called correctly during scene lifecycle events (e.g., connecting, disconnecting, foreground/background transitions). - Ensure
AppDelegate
handles app-wide events (e.g., push notifications, URL handling).
- Verify that
Test Multi-Window Support:
- On iPadOS, test opening multiple windows to ensure each scene is managed correctly.
- Verify state restoration for each scene.
Test Backward Compatibility:
- If supporting iOS 12 or earlier, test the app on older devices/simulators to ensure
AppDelegate
fallback works.
- If supporting iOS 12 or earlier, test the app on older devices/simulators to ensure
Step 6: Handle Advanced Features
State Restoration:
- Use
NSUserActivity
and scene restoration APIs to save and restore scene state. - Implement
scene(_:continue:)
andscene(_:willConnectTo:options:)
to handle state restoration.
- Use
URL Handling:
- Move URL handling from
application(_:open:options:)
inAppDelegate
toscene(_:openURLContexts:)
inSceneDelegate
for scene-specific URLs.
- Move URL handling from
Push Notifications:
- Handle scene-specific notifications in
SceneDelegate
if they affect a particular window’s content.
- Handle scene-specific notifications in
Best Practices
- Separation of Concerns: Keep
AppDelegate
for app-wide tasks (e.g., configuring analytics, push notifications) andSceneDelegate
for UI-related tasks (e.g., window management, view controller setup). - State Management: Ensure each scene maintains its own state, especially for multi-window apps.
- Modular Code: Refactor shared logic (e.g., view controller setup) into reusable functions to avoid duplication between
AppDelegate
andSceneDelegate
. - Testing: Thoroughly test lifecycle transitions, especially for multi-window scenarios and state restoration.
Common Challenges and Solutions
- Challenge: App crashes on iOS 13+ due to missing
SceneDelegate
.- Solution: Ensure
Info.plist
includesUIApplicationSceneManifest
and aSceneDelegate
class is implemented.
- Solution: Ensure
- Challenge: UI not appearing after migration.
- Solution: Verify that
scene(_:willConnectTo:options:)
sets up theUIWindow
and root view controller correctly.
- Solution: Verify that
- Challenge: State restoration not working.
- Solution: Implement state restoration APIs and test with
NSUserActivity
to ensure scene state is saved and restored.
- Solution: Implement state restoration APIs and test with
Conclusion
The transition from AppDelegate
to SceneDelegate
reflects Apple’s shift toward a more modular and flexible app architecture. By offloading UI management to SceneDelegate
, apps can support modern iOS features like multi-window interfaces while keeping AppDelegate
focused on app-wide tasks. The comparison table above clarifies the relationship between AppDelegate
and SceneDelegate
methods, aiding in a smoother migration. With careful refactoring and testing, migrating to SceneDelegate
enables a more robust and future-proof app structure.