The Scene Delegate In Xcode 11 And iOS 13

Written by Reinder de Vries on October 13 2019 in App Development

The Scene Delegate In Xcode 11 And iOS 13

What does the new SceneDelegate class in your iOS 13 project do? Since Xcode 11, the scene delegate is added automatically with the default iOS app project template. What does this scene delegate do, exactly?

In this article, we’ll dive into some of the changes of iOS 13 and Xcode 11. We’ll focus specifically on the scene and app delegates, 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

Ready? Let’s go.

  1. The App Delegate …
  2. … versus The Scene Delegate
  3. Using Scene Delegate With SwiftUI
  4. Using Scene Delegate With Storyboards
  5. Setting Up Your App Programmatically
  6. Further Reading

This article is written for Xcode 11 and iOS 13.

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 changes from iOS 12 to iOS 13, as we’ll soon discover.

Here’s what you typically use the app delegate for on iOS 12:

  • 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
}

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 most iOS apps only have one window. It 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” still escapes you, 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 is called the app switcher.

Learn how to build iOS apps

Get started with iOS 13 and Swift 5

Sign up for my iOS development course, and learn how to build great iOS 13 apps with Swift 5 and Xcode 11.

… versus 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 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.

In practical terms, the switch to using a scene delegate is most visible in Xcode 11 in three places:

  1. A new iOS project now has a SceneDelegate class, which is automatically created, 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

First off, the SceneDelegate class:

Xcode 11 Scene Delegate

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 on iOS 12. The 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.

2. App Delegate: Scene Sessions

On 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.

3. Info.plist Application Scene Manifest

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:

Application Scene Manifest, Xcode 11

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 12 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 11.

Using Scene Delegate With SwiftUI

Going forward, the most simple way to bootstrap an iOS 13 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:

Application Scene Manifest, SwiftUI, Xcode 11

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 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 11 project, with SwiftUI, by choosing File → New → Project…. Then, choose Single View App. Finally, select SwiftUI for User Interface.

Using Scene Delegate With Storyboards

Storyboards, next to XIBs, are an effective way to build UIs for your iOS apps. On iOS 13, that’s no different. Going forward, we’re going to see more SwiftUI apps, but for now, storyboards are more 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 window = UIWindow(windowScene: windowScene)

            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 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.

Want to quickly set up an app project with the above programmatic UIKit approach? David (@verebes1) has created an Xcode project template to create just that! You can read more on how to use it right here.

Learn how to build iOS apps

Get started with iOS 13 and Swift 5

Sign up for my iOS development course, and learn how to build great iOS 13 apps with Swift 5 and Xcode 11.

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!

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.