Back to blog

Scene Delegate vs. App Delegate Explained


Aasif Khan
By Aasif Khan | Last Updated on January 23rd, 2024 7:14 am | 5-min read

What does the SceneDelegate class in your iOS project do? In Xcode, the scene delegate and/or app delegate is added automatically for the default iOS app project template. What are these delegates for, exactly?

In this tutorial, we’ll dive into the scene and app delegates in Xcode, and how they affect SwiftUI, Storyboards and XIB based UIs.

You’ll learn about:

  • The app delegate and scene delegate
  • How the they work together to bootstrap your app
  • How to set up your app programmatically, with the scene delegate
  • Using the scene delegate with Storyboards and SwiftUI

Describe your app idea
and AI will build your App

The App Delegate …

You’re probably already familiar with the app delegate. It’s the starting point for an iOS app. Its application(_:didFinishLaunchingWithOptions:) function is the first function the operating system calls upon starting your app.

The AppDelegate class of your app adopts the UIApplicationDelegate protocol, which is part of the UIKit framework. The app delegate’s role has changed since iOS 12, as we’ll soon discover.

Here’s what you typically used the app delegate for:

  • Set up the first view controller of your app, called the root view controller
  • Configure app settings and startup components, such as logging and cloud services
  • Register push notification handlers, and respond to push notifications sent to the app
  • Respond to app lifecycle events, such as entering the background, resuming the app, or exiting the app (termination)

A boilerplate app that uses Storyboards has a surprisingly boring app delegate, because it only returns true. Like this:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
return true
}

A simple app that uses XIBs, and consequently sets up its own root view controller, looks something like this:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool

{
let timeline = TimelineViewController()
let navigation = UINavigationController(rootViewController: timeline)

let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)

window!.rootViewController = navigation
window!.makeKeyAndVisible()

return true
}

Why does the application(_:didFinishLaunchingWithOptions:) function return true? This boolean value indicates that the app can be started, based on the options passed to the function. It’s common to return true, but you can also return false when the app cannot start based on some URL resource or user activity.

In the above code, we’re creating a view controller, put it in a navigation controller, and assign that to the rootViewController property of a UIWindow object. This window object is a property of the app delegate, and it’s the one window our app has.

An app’s window is an important concept. In essence, the window is the app, and iOS apps only have one window (so far). This window houses your app’s User Interfaces (UIs), dispatches events to views, and it provides a main backdrop to display your app’s content. In a way, the concept of “windows” is what Microsoft Windows is named after – you know the one – and on iOS, that concept is no different. (Thanks, Xerox!)

OK, now that that’s out of the way, let’s move on to the scene delegate.

In case the concept of a “window” is unclear, check out the app switcher on your iPhone. Double-press the Home button or swipe up from the bottom of your iPhone and voilá, you’re seeing the windows of the apps that are currently running. This UI called the app switcher.

… vs. The Scene Delegate

On iOS 13 (and up), the scene delegate takes over some of the roles of the app delegate. Most importantly, the concept of a window is replaced (more or less) by that of a scene. An app can have more than one scene, and a scene now serves as the backdrop for your app’s User Interfaces and content.

Especially the concept of having one app with multiple scenes is interesting, because it allows you to build multi-window apps on iOS and iPadOS. Every text document in a word processor app could have its own scene, for example. Users can also create a copy of a scene, effectively running multiple instances of one app at a time.

Practically, using a scene delegate is most visible in Xcode in 3 places:

  1. A new iOS project has a SceneDelegate class, which is automatically created for the default app project, that includes familiar lifecycle events such as active, resign and disconnect
  2. The AppDelegate class has two new functions related to scene sessions, called application(_:configurationForConnecting:options:) and application(_:didDiscardSceneSessions:)
  3. The Info.plist property list gets an Application Scene Manifest, listing scenes that are part of this app, including their class, delegate and storyboard names

Let’s go over ’em one by one.

  1. Scene Delegate Class
  2. First off, the SceneDelegate class:

    A scene delegate’s most important function is scene(_:willConnectTo:options:). In a way, it’s most similar to the role of the application(_:didFinishLaunchingWithOptions:) function of the app delegate. This function is called when a scene is added to the app, so it’s the perfect point to configure that scene. In the above code, we’re manually setting up the view controller stack, but more about that later.

    It’s important to note here that the SceneDelegate uses delegation, of course, and that one delegate typically responds to any scene. You use one delegate to configure all scenes in your app.

    The SceneDelegate also has these functions:

    • sceneDidDisconnect(_:) is called when a scene has been disconnected from the app (Note that it can reconnect later on.)
    • sceneDidBecomeActive(_:) is called when the user starts interacting with a scene, such as selecting it from the app switcher
    • sceneWillResignActive(_:) is called when the user stops interacting with a scene, for example by switching to another scene
    • sceneWillEnterForeground(_:) is called when a scene enters the foreground, i.e. starts or resumes from a background state
    • sceneDidEnterBackground(_:) is called when a scene enters the background, i.e. the app is minimized but still present in the background

    See the symmetries between those functions? Active/inactive, background/foreground, and “disconnect”. These are typical lifecycle events for any application or process.

  3. App Delegate: Scene Sessions
  4. Since iOS 13, the AppDelegate class now includes two more delegate functions related to the management of scene sessions. When a scene is created in your app, a scene session object tracks all information related to that scene.

    These functions are:

    • application(_:configurationForConnecting:options:), which needs to return a configuration object when creating a new scene
    • application(_:didDiscardSceneSessions:), which is called when your app’s user closed one or more scenes via the app switcher

    At the moment, the scene session is used to designate a role to a scene, such as “External Display” or “CarPlay”. It’s also used to restore the state of a scene, which is useful if you want to use state restoration. State restoration allows you to preserve and recreate a UI between app launches. You can also assign user info to the scene session, which is effectively a dictionary you can put anything in.

    The application(_:didDiscardSceneSessions:) is quite straightforward. It’s called when the user of your app closes one or more scenes via the app switcher. You can use this function to dispose of resources that these scenes used, because they’re not needed anymore.

    It’s important to contrast this function with sceneDidDisconnect(_:), which is merely called when a scene disconnects, but isn’t necessarily discarded. It may reconnect, whereas application(_:didDiscardSceneSessions:) marks the moment a scene is quit, with the app switcher.

  5. Info.plist Application Scene Manifest
  6. Every scene your app supports needs to be declared in an Application Scene Manifest. In short, the manifest lists every scene your app supports. Most apps only have one scene, but you could create more, such as specific scenes for responding to push notifications or specific actions.

    The Application Scene Manifest manifest is part of the Info.plist file, which is a well-known place for configuration values of your app. This property list includes values like your app’s name, version, supported interface orientations, and now, the different scenes it supports.

    It’s important to note here that you declare types of sessions, and not the sessions themselves per se. Your app can support one scene, create copies of that scene, and use that to create a multi-windowed app.

Here’s an overview of the manifest, as part of Info.plist:

At the top level, you see the Application Scene Manifest entry. Below it, the entry Enable Multiple Windows, which needs to be set to YES to support multiple windows. Below that, the array Application Session Role is used to declare scenes within the app. Another item can be used to declare scenes for external screens.

The most important information is kept inside the items in the Application Session Role array. This entry lists the following:

  • The name of this configuration, which needs to be unique
  • The class name of the scene, UIWindowScene
  • The class name of the delegate for this scene, which is normally SceneDelegate
  • The name of the storyboard that contains the initial UI for this scene

That storyboard name item might remind you of the Main Interface setting, which can be found in the Project Properties configuration of an Xcode project. Within a basic iOS app, if you don’t use scenes, this is where you’d set or change the main storyboard now.

How do the SceneDelegate, the scene sessions in the AppDelegate, and the Application Scene Manifest play together to create multi-window apps?

  • First, we’ve looked at the SceneDelegate class. It manages the lifecycle of scenes, responding to events like sceneDidBecomeActive(_:) and sceneDidEnterBackground(_:).
  • Then, we checked out the new functions in the AppDelegate class. It manages scene sessions, provides configuration data for scenes, and responds to the user discarding a scene.
  • Finally, we looked at the Application Scene Manifest. It lists scenes your app supports, and connects them to a delegate class and an initial storyboard.

Awesome! Now that we’ve got a playing field, let’s find out how scenes affects building UIs in Xcode.

Using Scene Delegate With SwiftUI

Going forward, the most simple way to bootstrap an iOS app is by using SwiftUI. In short, a SwiftUI app mostly relies on the SceneDelegate to set up the initial UI of the app.

First, here’s how the Application Scene Manifest looks:

Are you building a primarily SwiftUI-based app? It may make sense to use the SwiftUI App setting for App Lifecycle, when creating the app. This adds an App protocol implementation to your app, which is the starting point of your SwiftUI app. Learn more here: SwiftUI’s App Lifecycle Explained

It’s fairly standard for a default app. What stands out, is that the Default Configuration doesn’t have a Storyboard Name set. Remember, if you want to support multiple windows, you’ll need to set Enable Multiple Windows to YES.

We’re going to skip the AppDelegate, because it’s fairly standard – in this scenario, it’ll only return true. Easy-peasy.

Next up, the SceneDelegate class. As we’ve discussed before, the scene delegate is responsible for setting up the scenes of your app, as well as set up their initial views.

Like this:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

let contentView = ContentView()

if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}

What’s going on in the above code?

  • First, it’s important to consider that the scene(_:willConnectTo:options:) delegate function is called when a new scene is added to the app. It gets provided a scene object (and a session). This UIWindowScene object is created by the app, so you’re not doing that manually.
  • Then, the window property is used as well. Apps still use UIWindow objects, but now they’re part of the scene. In the code, within the if let block, you can clearly see how a UIWindow object is initialized by using the scene.
  • A root view controller is assigned, the local window constant is assigned to the window property, and the window is “made key and visible”, i.e. put the window at the front of the app’s UI.
  • Specific to SwiftUI, a ContentView is created, and added as a root view controller by making use of a UIHostingController. This controller is used to put SwiftUI based views on screen.
  • Last but not least, it’s worth noting that the scene parameter, of type UIScene, is in fact an object of type UIWindowScene. That’s what the optional casting with as? is for. (So far, created scenes are typically of type UIWindowScene, but my guess is that in the future, we’ll see more types of scenes.)

All this may seem complicated, but from a high-level overview, it’s quite simple:

  • The scene delegate configures the scene, at the right time, when scene(_:willConnectTo:options:) is called.
  • The app delegate, and Manifest, have a default configuration, that doesn’t involve a storyboard.
  • The scene(_:willConnectTo:options:) function creates a SwiftUI view, puts it in a hosting controller, assigns that to the root view controller of the window property, and puts that window at the front of the app UI.

Awesome! Let’s move on.

You can set up a basic Xcode project, with SwiftUI, by choosing File → New → Project…. Then, choose Single View App. Finally, select SwiftUI for User Interface. Learn more here: How To Create a New Xcode App Project

Using Scene Delegate With Storyboards

Storyboards, next to XIBs, are an effective way to build UIs for your iOS apps. Going forward, we’re going to see more SwiftUI apps, but for now, storyboards are still quite common.

Interestingly, you don’t have to do anything to create a new iOS project in Xcode, with Storyboards, as far as the new scene delegate goes. Just choose File → New → Project…. Then, choose Single View App. Finally, select Storyboard for User Interface, and you’re done.

Here’s how it’s set up:

  • As explained earlier, you’ll find the Main storyboard in the Application Scene Manifest inside Info.plist
  • The app delegate, by default, will use the default scene configuration.
  • The scene delegate, by default, sets up a UIWindow object, and uses the Main.storyboard to create the initial UI.

That’s it. Really short chapter, this one…

Setting Up Your App Programmatically

Plenty of developers create their UIs programmatically, and with the dawn of SwiftUI, we’re only going to see more of that. What if you don’t use storyboards, but you use individual XIBs to create your app’s UI? Let’s check out how the scene delegate fits in here.

First off, the app delegate and Application Scene Manifest are exactly as you’d expect them: set to their defaults. We’re not using a storyboard, and instead, we’re going to set up the initial view controller in the scene(_:willConnectTo:options:) function inside the SceneDelegate class.

Like this:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
{
if let windowScene = scene as? UIWindowScene {

let window = UIWindow(windowScene: windowScene)
let timeline = TimelineViewController()

let navigation = UINavigationController(rootViewController: timeline)
window.rootViewController = navigation

self.window = window
window.makeKeyAndVisible()
}
}

Here’s what happens:

  • Just like before, we have that window property of type UIWindow. It’s initialized with the windowScene object, which is type cast from the scene parameter.
  • Within the if let block, familiar code appears: this is exactly how you’d set up a root view controller on iOS 12 and earlier, with the app delegate. You initialize a view controller, put it in a navigation controller, and assign that to the rootViewController property.
  • Finally, the local window constant is assigned to the window property, and the window is “made key and visible”, i.e., put on screen at the front.

Quite simple, right? The core of using the scene delegate in this way, is moving some of your code from the app delegate to the scene delegate, and properly configuring the Application Scene Manifest.

Looking to add scene support to an existing iOS app project? Check out these instructions.

Further Reading

Pfew! That’s quite a bit of work, for such a simple component. As we’ve discussed, the scene delegate allows you to add multiple windows to your app.

You’ve learned how to set up the scene delegate for SwiftUI, storyboards, and programmatically. We’ve also looked at the 3 components that make scenes work: the app delegate, the scene delegate, and the Application Scene Manifest. Awesome!


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts