
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:
Ready? Let’s go.
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:
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.
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:
SceneDelegate class, which is automatically created for the default app project, that includes familiar lifecycle events such as active, resign and disconnectAppDelegate class has two new functions related to scene sessions, called application(_:configurationForConnecting:options:) and application(_:didDiscardSceneSessions:)Info.plist property list gets an Application Scene Manifest, listing scenes that are part of this app, including their class, delegate and storyboard namesLet’s go over ’em one by one.
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 switchersceneWillResignActive(_:) is called when the user stops interacting with a scene, for example by switching to another scenesceneWillEnterForeground(_:) is called when a scene enters the foreground, i.e. starts or resumes from a background statesceneDidEnterBackground(_:) is called when a scene enters the background, i.e. the app is minimized but still present in the backgroundSee the symmetries between those functions? Active/inactive, background/foreground, and “disconnect”. These are typical lifecycle events for any application or process.
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 sceneapplication(_:didDiscardSceneSessions:), which is called when your app’s user closed one or more scenes via the app switcherAt 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.
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:
UIWindowSceneSceneDelegateThat 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?
SceneDelegate class. It manages the lifecycle of scenes, responding to events like sceneDidBecomeActive(_:) and sceneDidEnterBackground(_:).AppDelegate class. It manages scene sessions, provides configuration data for scenes, and responds to the user discarding a scene.Awesome! Now that we’ve got a playing field, let’s find out how scenes affects building UIs in Xcode.
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?
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.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.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.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.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:
scene(_:willConnectTo:options:) is called.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 . Then, choose Single View App. Finally, select SwiftUI for User Interface. Learn more here: How To Create a New Xcode App Project
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 . Then, choose Single View App. Finally, select Storyboard for User Interface, and you’re done.
Here’s how it’s set up:
Main storyboard in the Application Scene Manifest inside Info.plistUIWindow object, and uses the Main.storyboard to create the initial UI.That’s it. Really short chapter, this one…
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:
window property of type UIWindow. It’s initialized with the windowScene object, which is type cast from the scene parameter.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.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.
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!
Want to learn more? Check out these resources: