With iOS 13 Apple introduced multiple window support. Users of your iPad app can now open up multiple windows, also called scenes, side by side which allows for many new powerful workflows. As before iOS 13 we were used to having a one to one mapping between our application and our UI, adding support for multiple windows requires us to think differently about our app and its structure. Suddenly those assumptions you made based on having only one window don’t hold up anymore. This blog post will take you to the first step of adding support for multiple windows to your app, which is adopting the new UISceneDelegate API.

Revisiting the Application life cycle

Before going over the changes in the application lifecycle lets have a look at how things were pre-iOS 13.

App launch

One of the first things the system does when an app is launched is executing the UIApplicationMain(_:_:_:_:) function, which sets up the main event loop and creates the UIApplication shared instance and its delegate.

UIApplication

The application object represents the state of the system as a system process. For example, it handles the initial routing of incoming user events and manages the windows that are associated with the app.

UIApplicationDelegate

UIApplication defines the UIApplicationDelegate protocol which is implemented by the AppDelegate. As subclassing of UIApplication is rare for most projects this is the entry point of the application. It is through the app delegate that the application informs the app of significant runtime events. In the app delegate you typically:

  • Setup up core objects such as your database, networking stack, etc.
  • Create the window and set up the main user interface.
  • Respond to process-level events for example launch, termination or incoming push notifications.

What changes with the new scene API

To understand the changes to the application lifecycle you need to know about the following core objects of the new scene API.

UIScene and UISceneDelegateClassName

Now that apps can run multiple instances of its UI at the same time the app delegate is no longer responsible for setting up the UI and handling lifecycle UI related lifecycle events, such as going to the foreground or background. Instead, every instance of your apps UI is now represented by an UIScene object. And similarly to how UIApplication would notify your app of events through the app delegate, a scene does this to its associated scene delegate. Each scene has its own scene delegate. You typically work with a UIWindowScene and its corresponding UIWindowSceneDelegate.

UISceneSession

Every scene is managed by an UISceneSession, which holds all the configuration data and state restoration data needed to create a scene object. While scenes can be destroyed by the system when it needs to free up memory the application will keep a reference to the session until it is closed.

Changes to the AppDelegate

When you add a scene delegate to your project the app delegate won’t receive any more those events which now should be handled by the scene delegate. In addition to that your app delegate can have two new methods:

application(_:configurationForConnecting:options:)

Called right before a new scene is created, this method allows you to specify the configuration that should be used for creating the new scene through a UISceneConfiguration object. This object holds all the information needed to create a new scene, e.g the type of SceneDelegate to use and the name of the storyboard containing your initial UI. You can typically create a new scene delegate subclass for each scene in your app. For example in a recipe app in addition to the main scene you could have another scene that is dedicated to adding a new recipe to the app. You can also specify the Role of the scene which determines where the scene is displayed, which could be just on the iPad or on an external device e.g. car play.

application(_:didDiscardSceneSessions:)

This method gets called right before a scene is destroyed, e.g when the user removes a scene through the app switcher. Here you typically do things like persist data and release objects that are not needed anymore. It could be that a scene got discarded while the app was not running anymore, in which case this method will be called on the next app launch.

Lifecycle methods of the scene delegate

The most important method in your scene delegate is scene(_:willConnectTo:options:) which gets called right after the creation of a new scene. Here you set up the window and the root view controller of the scene. This will look familiar to what you used to do in application(_:didFinishLaunchingWithOptions:). Most other methods are very familiar to the methods you would usually implement on the app delegate for example sceneDidBecomeActive(_:) or sceneWillResignActive(_:). However, keep in mind that these will be called for the particular scene and not for the whole application.

Adding a scene delegate to an existing project

Now that we have gained some knowledge on the new scene API we can go ahead and implement it in our project.

Step 1

Add a UIApplicationSceneManifest to the info.plist. This will indicate that your app supports scenes and that you opt-out of using the app delegate to manage the UI. It also allows you to declare all the different types of scenes your app can support.

There are two ways in which you can add a manifest to your existing app:

  1. Click on your project in the project navigator -> click on your app’s target -> check the Supports multiple windows checkbox under deployment info.
  2. Right-click on the info.plist and choose Open as -> Source Code and copy-paste the following code snippet into it.
<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <true/>
    <key>UISceneConfigurations</key>
    <dict>
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneConfigurationName</key>
                <string>Default Configuration</string>
                <key>UISceneDelegateClassName</key>
                <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                <key>UISceneStoryboardFile</key>
                <string>Main</string>
            </dict>
        </array>
    </dict>
</dict>

If you don’t want to support multiple scenes right away you can change the value of the UIApplicationSupportsMultipleScenes entry to false. Omit the UISceneStoryboardFile key if you don’t use storyboards.

Step 2

Go to your app delegate and implement application(_:configurationForConnecting:options:). A basic implementation looks something like this:

return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)

Step 3

Create a new UIResponder subclass called SceneDelegate, let it conform to the UIWindowSceneDelegate and add a window property. Then implement scene(_:willConnectTo:options:). UIKit will automatically setup a window with your storyboards initial view controller. If you don’t use storyboards you set the window up as follows:

guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
// Provide your apps root view controller
window.rootViewController = RootViewController()
self.window = window
window.makeKeyAndVisible()

Backwards compatibility

As with most new APIs, the new scene API is iOS 13 only. This means that if your app supports previous versions of iOS you will need to manage your window and life cycle in both the app delegate and in the scene delegate. You can manage this by encapsulating your logic into dedicated objects which you can then use from both types of delegates. This is, in general, good practice as it keeps your AppDelegate and/or SceneDelegate light and allows for easier unit testing.

UIApplication deprecations

Because a scene and a delegate are now responsible for a single window some of the properties of UIApplication are deprecated. From now on if you want to get a hold of a window you can’t use the keyWindow property anymore, instead use the windows property. Different windows can also have different status bar styles so for that refer to the view controller or window scene.

Conclusion

Congratulations, that’s all that is needed to get started with the new scene API. Even if you won’t support multiple windows it is a good idea to make your project future proof and adopt the new scene API. In my next blog post, I will dive deeper into the details of supporting multiple scenes and how it can enhance your app. Contact me on Twitter @kairadiagne if you have any questions, comments or feedback.