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
trueto 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,
AppDelegatehandles both app and UI lifecycle events. In iOS 13+, UI-related tasks (e.g.,UIWindowsetup) move toSceneDelegate. - Granularity:
SceneDelegatemethods are scene-specific, enabling multi-window support, whileAppDelegatemethods are app-wide. - New Responsibilities:
AppDelegategains 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
SceneDelegateis not available in earlier versions. - If the app must support iOS 12 or earlier, implement conditional logic to handle both
AppDelegateandSceneDelegate.
- Ensure the app’s deployment target is iOS 13.0 or later, as
Add Scene Support to Info.plist:
- Open the app’s
Info.plistfile and add theUIApplicationSceneManifestkey 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 totrueto enable multi-window support (optional).UISceneConfigurationName: A unique name for the scene configuration.UISceneDelegateClassName: Specifies theSceneDelegateclass.UISceneStoryboardFile: (Optional) Specifies the storyboard for the scene.
- Open the app’s
Remove UIWindow from AppDelegate:
- If your
AppDelegatecreates aUIWindow, remove this code, as theSceneDelegatewill 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
UIWindowand setting the root view controller) fromapplication(_:didFinishLaunchingWithOptions:)inAppDelegatetoscene(_:willConnectTo:options:)inSceneDelegate.
- In Xcode, create a new Swift file named
Update AppDelegate:
- Modify
AppDelegateto 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
configurationForConnectingmethod 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
AppDelegatetoSceneDelegate. - Example: Move
windowsetup 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
SceneDelegateto 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
UIWindowand state management. - Handle scene creation and destruction appropriately in
SceneDelegatemethods.
- 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
#availablecheck to ensure compatibility with older iOS versions.
Step 5: Test the Migration
Test Lifecycle Events:
- Verify that
SceneDelegatemethods are called correctly during scene lifecycle events (e.g., connecting, disconnecting, foreground/background transitions). - Ensure
AppDelegatehandles 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
AppDelegatefallback works.
- If supporting iOS 12 or earlier, test the app on older devices/simulators to ensure
Step 6: Handle Advanced Features
State Restoration:
- Use
NSUserActivityand 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:)inAppDelegatetoscene(_:openURLContexts:)inSceneDelegatefor scene-specific URLs.
- Move URL handling from
Push Notifications:
- Handle scene-specific notifications in
SceneDelegateif they affect a particular window’s content.
- Handle scene-specific notifications in
Best Practices
- Separation of Concerns: Keep
AppDelegatefor app-wide tasks (e.g., configuring analytics, push notifications) andSceneDelegatefor 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
AppDelegateandSceneDelegate. - 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.plistincludesUIApplicationSceneManifestand aSceneDelegateclass is implemented.
- Solution: Ensure
- Challenge: UI not appearing after migration.
- Solution: Verify that
scene(_:willConnectTo:options:)sets up theUIWindowand root view controller correctly.
- Solution: Verify that
- Challenge: State restoration not working.
- Solution: Implement state restoration APIs and test with
NSUserActivityto 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.