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. Returning false indicates 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 in applicationDidEnterBackground.
  • 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 with application(_: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 via application(_: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 a UISceneConnectionOptions object 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 NSUserActivity or UIStateRestoring), 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.delegate to get the window; instead, retrieve the window from the active scene’s delegate. In UIKit, use UIApplication.shared.connectedScenes.first?.delegate but 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.