measurement-and-instrumentation
Understanding the Role of App Delegates and Scene Delegates in Ios
Table of Contents
Understanding the Role of App Delegates and Scene Delegates in iOS Development
Since the earliest versions of iOS, the UIApplicationDelegate protocol has been the backbone of iOS app lifecycle management. However, the introduction of multi-window support in iOS 13 fundamentally changed how developers handle user interfaces. Apple introduced the UISceneDelegate protocol to decouple app-wide responsibilities from per-window management. Understanding the precise roles of these two delegates is essential for building modern, multi-window iOS applications—whether targeting iPhones, iPads, or Mac Catalyst. This article provides a comprehensive exploration of both delegates, their lifecycle methods, how they interact, and best practices for using them effectively.
What is an App Delegate?
The App Delegate is the entry point and central coordinator for an iOS application. It conforms to the UIApplicationDelegate protocol and is responsible for handling high-level app events that affect the entire application. These events include app launch, termination, memory warnings, background fetch, and remote notification registration. The App Delegate object is instantiated automatically by the system at launch and remains alive for the lifetime of the process.
Core Lifecycle Methods
The App Delegate defines several critical methods that the system calls in response to app state changes:
application(_:didFinishLaunchingWithOptions:)— Called after the app has launched. This is where you initialize third-party SDKs, configure core data stacks, set up global appearance, and restore the app’s state. Returningfalseindicates the app cannot handle the URL or activity that caused the launch.applicationDidBecomeActive(_:)— Invoked when the app transitions from inactive to active state. Use this to resume paused tasks, refresh UI, or restart animations.applicationWillResignActive(_:)— Called when the app is about to become inactive (e.g., incoming phone call, user presses the Lock button). Save state, pause games, and reduce resource usage.applicationDidEnterBackground(_:)— Sent when the app moves to the background. Perform final cleanup, release shared resources, and schedule background tasks.applicationWillEnterForeground(_:)— Called just before the app returns to the foreground after being backgrounded. Undo changes made inapplicationDidEnterBackground.applicationWillTerminate(_:)— Invoked when the app is about to be terminated. Save critical data and release resources. Note: this is not called on modern iOS versions for apps in the background unless specifically killed.
Additional Responsibilities
Besides lifecycle management, the App Delegate handles several app-wide services:
- Remote and Local Notifications: Register for remote notifications via
application(_:didRegisterForRemoteNotificationsWithDeviceToken:)and handle incoming notifications withapplication(_:didReceiveRemoteNotification:fetchCompletionHandler:). - Handling URLs and Handoff: The App Delegate responds to custom URL schemes and Universal Links through
application(_:open:options:)and manages Handoff activity viaapplication(_:continue:restorationHandler:). - Memory Warnings:
applicationDidReceiveMemoryWarning(_:)allows the app to release caches, images, and other disposable resources. - Background Fetch: Implement
application(_:performFetchWithCompletionHandler:)to download content periodically while the app is in the background. - Watch Connectivity and App Groups: Coordinate data sharing between the iOS app and companion Watch app through the App Delegate.
Historical Context
Before iOS 13, the App Delegate was also responsible for managing the app’s single window instance. Developers set the root view controller inside application(_:didFinishLaunchingWithOptions:). This design was simple but limiting—it could not support multiple simultaneous windows. The introduction of scenes in iOS 13 moved window management to separate delegate objects, leaving the App Delegate free to focus exclusively on app-wide concerns.
What is a Scene Delegate?
With iOS 13 and iPadOS 13, Apple introduced the UISceneDelegate protocol to support multiple windows per app. A scene represents one instance of your app’s user interface, typically backed by a UIWindowScene object. Each scene has its own lifecycle and can be displayed on a separate display or in a separate space on iPad. The Scene Delegate manages a specific scene’s lifecycle, user interface state, and interactions.
Enabling Multi-Window Support
To use scenes, you must opt in by configuring your app’s Info.plist with the “Application Scene Manifest” key. This manifest defines the scene configurations, including delegate class names and session roles. Without this manifest, iOS treats the app as single-window and continues using the legacy App Delegate window management.
Core Scene Lifecycle Methods
The Scene Delegate implements methods analogous to the App Delegate but scoped to one scene:
scene(_:willConnectTo:options:)— Called when a new scene is about to be added to the app. This is where you set up the scene’s root view controller and initial UI. You receive aUISceneConnectionOptionsobject that provides information about the URL, activity, or shortcut that triggered the scene.sceneDidBecomeActive(_:)— The scene becomes active (foreground and in focus). Resume paused activities, start animations, and refresh data.sceneWillResignActive(_:)— The scene is about to become inactive (e.g., multitasking gesture, app switcher). Save state and pause operations.sceneDidEnterBackground(_:)— The scene moves to the background. Release resources, save data, and schedule background tasks for this scene.sceneWillEnterForeground(_:)— The scene is about to become active in the foreground. Undo background changes.sceneDidDisconnect(_:)— Called when the system removes the scene from the app (e.g., user closes a window on iPad, or the app moves to background and the system decides to discard the scene). Perform final cleanup specific to that scene.
Scene Sessions and Roles
Each scene is associated with a UISceneSession object that persists across launches. Sessions can be reused or created anew. The session contains a role property that indicates whether the scene is for the main app interface (.windowApplication) or an external display (.windowApplicationOnExternalDisplay). The Scene Delegate can check the session’s role to adapt its behavior.
Scene Delegate in SwiftUI
For SwiftUI apps, the Scene Delegate is often auto-generated when you start a new project targeting iOS 14+. However, you can still create a custom Scene Delegate by using the UIApplicationDelegateAdaptor property wrapper in your @main structure. SwiftUI’s WindowGroup scene implicitly uses UISceneConfiguration, but you can override delegate behavior by conforming to UISceneDelegate in an adaptor.
Key Differences Between App Delegate and Scene Delegate
While both delegates manage lifecycle, their scope and responsibilities differ significantly:
- Scope: The App Delegate operates at the application process level; there is only one per app. The Scene Delegate operates at the scene (or window) level; you may have multiple scene delegates running simultaneously.
- Introduction: App Delegate has existed since iOS 2. Scene Delegate was introduced in iOS 13 to enable multi-window support.
- Window Management: In legacy apps, the App Delegate owns the window. In scene-based apps, each Scene Delegate owns its scene’s window. The App Delegate no longer accesses the window directly.
- State Preservation: Apps using scenes should implement state preservation and restoration at the scene level (via
NSUserActivityorUIStateRestoring), not solely through the App Delegate. - Background Tasks: In general, background tasks can be triggered app-wide (via App Delegate) or per-scene (via Scene Delegate). However, certain background modes like background fetch are still managed by the App Delegate.
How They Work Together in a Modern App
In a scene-based iOS app, the App Delegate and Scene Delegate form a layered architecture. The App Delegate initializes shared infrastructure—logging, analytics, core database—and handles events that don’t belong to a particular scene. When the system needs to display new content, it creates a scene and associates a Scene Delegate. The Scene Delegate then builds the UI, connects to the scene session, and manages user interactions within that scene.
Coordination During Launch
When a user opens the app, the system calls the App Delegate’s application(_:didFinishLaunchingWithOptions:). After this returns, the system creates a scene and calls scene(_:willConnectTo:options:) on the Scene Delegate. If the app is reopened via Handoff or a shortcut, the UISceneConnectionOptions delivers the relevant activity, URL, or user activity to the appropriate Scene Delegate.
State Restoration Across Scenes
State restoration is crucial for seamless multitasking. Each scene should encode its state using NSUserActivity in the Scene Delegate’s stateRestorationActivity(for:) method. The App Delegate is responsible for providing activity types via application(_:configurationForConnecting:options:) when creating a scene session. This coordination ensures that reopened scenes restore their content—for example, a document app can reopen the exact file the user was editing in each window.
Handling External Displays
On iPad, a scene can be created on an external monitor via “AirPlay” or “Sidecar.” By inspecting the UISceneSession role, the Scene Delegate can adjust its UI to match the resolution and capabilities of the external display. The App Delegate remains unaware of which display the scene is on, keeping responsibilities cleanly separated.
Practical Example: Setting Up a Scene-Based iOS App
Let’s walk through the steps to configure a new UIKit app with scene delegates. Assume you’re starting from an empty Xcode project with the “Use Scene Delegate” option enabled.
Step 1: Info.plist Configuration
Xcode automatically adds the “Application Scene Manifest” entry. Open the Info.plist and verify it contains a dictionary with:
- Enable Multiple Windows: YES (if you want to allow multiple windows; otherwise NO).
- Scene Configurations: An array of dictionaries, each defining a scene role (e.g., “Window Application”) and a delegate class name (e.g., “$(PRODUCT_MODULE_NAME).SceneDelegate”).
For a single-window app, you can leave multiple windows disabled, but you still need the scene configuration to use Scene Delegate.
Step 2: Implement App Delegate
In AppDelegate.swift, remove any window property. Instead, implement only app-wide methods:
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Configure analytics, core data, third-party SDKs
return true
}
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Return a scene configuration to use for the new scene
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
// Called when the user discards a scene (e.g., closing a window)
}
}
Step 3: Implement Scene Delegate
In SceneDelegate.swift, manage the window and scene lifecycle:
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let viewController = ViewController()
window.rootViewController = viewController
self.window = window
window.makeKeyAndVisible()
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called when the scene is being removed from the app
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Resume tasks
}
func sceneWillResignActive(_ scene: UIScene) {
// Pause tasks
}
}
Step 4: Handling Scene Connection Options
The connectionOptions in scene(_:willConnectTo:options:) provides shortcuts, URL contexts, and user activities. For example, to open a specific document when the user taps a file:
if let urlContext = connectionOptions.urlContexts.first {
// Intialize a document viewer with the URL
urlContext.url.startAccessingSecurityScopedResource()
// Load document and set root view controller
}
This separation keeps the App Delegate lean and each Scene Delegate focused on its own window.
Best Practices and Common Pitfalls
When to Use App Delegate vs. Scene Delegate
- Use the App Delegate for initialization that must happen exactly once per app launch, such as crash reporting, user authentication, or database migration.
- Use the Scene Delegate for UI setup, view controller instantiation, and scene-specific state management.
- Do not access the shared
UIApplication.shared.delegateto get the window; instead, retrieve the window from the active scene’s delegate. In UIKit, useUIApplication.shared.connectedScenes.first?.delegatebut prefer passing scenes explicitly.
Migrating from Legacy Apps
If you have an existing single-window app that uses the App Delegate for window management, you can adopt scenes by adding the scene manifest and moving window-related code to a new Scene Delegate. Test thoroughly, especially if your app depends on UIApplication.shared.keyWindow, which is deprecated in iOS 13+.
Memory and Performance Considerations
Each scene consumes memory and resources. On iPad, the system may disconnect scenes when memory is low. Implement sceneDidDisconnect(_:) to release scene-specific resources. Avoid storing large caches or data in the Scene Delegate; use the App Delegate for app-wide caches that can survive scene disconnection.
Handling Delegates in SwiftUI
In SwiftUI, you can attach a custom App Delegate via @UIApplicationDelegateAdaptor. However, for scene management, SwiftUI’s WindowGroup automatically uses the default scene configuration. If you need custom scene behavior (e.g., handling URL contexts per window), create a class conforming to UISceneDelegate and assign it in the manifest or via the .defaultSceneConfiguration modifier.
Conclusion
The App Delegate and Scene Delegate are complementary pillars of modern iOS app architecture. The App Delegate provides a single point of control for application-level events, while Scene Delegates manage per-window lifecycle and user interface. By understanding their distinct responsibilities and how they collaborate, you can build apps that gracefully support multiple windows, state restoration, and responsive user experiences across all iOS devices. For further reading, consult Apple’s UIApplicationDelegate documentation, the UISceneDelegate documentation, and the Managing Your App’s Lifecycle guide.