On iOS 13 and later, applications can support multiple windows on iPad. This is a welcome addition and it takes multitasking to another level on iPad. On iOS 12 and earlier, applications manage one user interface and, typically, one window. To add support for multiple windows, Apple was forced to drastically redesign the application and user interface life cycles on iOS.

What do these changes mean for you and your applications? It is more important than ever to have a good understanding of the application and user interface life cycles on iOS. We explore both life cycles in detail in this and the next episodes.

Prepare for Significant Changes

Every application running on iOS is affected by these changes, even if the application doesn't support iPad or multiple windows. The new API introduced in iOS 13 needs to be adopted by every application running on iOS.

What does that mean for you? The bad news is that the changes are significant. The learning curve for developers new to iOS has become quite a bit steeper, even if you are building an application that doesn't support iPad or multiple windows.

The good news is that you have time. Existing applications don't need to implement the new API today. That said, it is likely that Apple will require developers to adopt the new API at some point in the future.

If you are starting a brand new project that targets iOS 13 and later, then I recommend adopting the new API. It is important that you become familiar with the new API sooner rather than later. Applications that want to support multiple windows have no other option but to adopt the new API.

Apple has invested heavily into the development of iOS 13 and iPadOS. Multiple windows on iPad is a major feature and it is here to stay. This means that you need to embrace the new API at some point.

What Has Changed?

What do I mean by the new API? To understand what has changed, you first need a good understanding of the application and user interface life cycles on iOS 12 and earlier.

iOS 12 and Earlier

On iOS 12 and earlier, the application and user interface life cycles are tightly coupled. Let's take a closer look at both life cycles. When the user taps the icon of your application, the system create the process for your application, launches your application, and invokes the UIApplicationMain(_:_:_:_:) function. This function creates an instance of the UIApplication class. Every application has a single instance of the UIApplication class. The singleton is accessible through the shared class method of UIApplication.

The UIApplication singleton has a delegate object that conforms to the UIApplicationDelegate protocol. When you create a new project by choosing the Single View App template, Xcode creates the AppDelegate class for you. It conforms to the UIApplicationDelegate protocol and is often considered the root object of your application.

iOS 12 and Earlier

The UIApplication singleton notifies the application delegate of a number of application life cycle events, for example, when the application has finished launching and when it is about to be terminated by the system. What I want you to remember is that an application is tied to one process and one UIApplication instance. This is true for iOS 12 and earlier and it remains true for iOS 13 and later.

When the system launches your application, the application delegate is notified of this event through its application(_:willFinishLaunchingWithOptions:) and application(_:didFinishLaunchingWithOptions:) methods. You typically prepare your application for launch in application(_:didFinishLaunchingWithOptions:). In this method, you set up the user interface by creating the application window or the window is created for you if you use storyboards.

The application delegate is also notified of user interface life cycle events, for example, when the application enters the foreground or the background. The application delegate is notified of application life cycle events as well as user interface life cycle events.

iOS 12 and Earlier

In summary, an application is tied to one process managed by the system. This simply means that it is not possible to run multiple instances of your application on iOS. The UIApplication singleton manages one user interface and one window. The application delegate is notified of application and user interface life cycle events. This makes sense because there is only one instance of your application running and that instance manages one user interface.

iOS 13 and Later

On iOS 13 and later, an application continues to be tied to one process managed by the system. Your application is tied to one process and that implies that there is one UIApplication instance. You can still access the UIApplication singleton through the shared class method of UIApplication. The UIApplication singleton has an application delegate that conforms to the UIApplicationDelegate protocol.

iOS 13 and Later

What has changed is that the application delegate is no longer notified of user interface life cycle events and that it is possible for an application to have multiple user interfaces or scenes. The application delegate is no longer notified when the application enters the foreground or the background if the application runs on iOS 13 or later and adopts the scene-based API.

How the user interface is set up, managed, and torn down is quite different in a scene-based application. The application delegate is no longer responsible for creating and managing the window of the application. That responsibility is shifted to another delegate, the scene delegate, an object that conforms to the UIWindowSceneDelegate protocol. This implies that any events related to the user interface life cycle, for example, the application entering the foreground or the background, are handled by the scene delegate, not the application delegate.

iOS 13 and Later

On iOS 13 and later, applications can support multiple scenes on iPad. Each scene has its own state. One scene can be in the foreground while another scene can be in the background. This means that it no longer makes sense to put the application delegate in charge of handling user interface life cycle events. Each scene has its on UIWindowSceneDelegate object that handles the user interface life cycle events of the scene it is tied to.

One Application, Multiple Scenes

It is important to understand that multiple scenes are tied to one process and one UIApplication instance. There is one application delegate and one or more scene delegates. In other words, multiple scenes are not the result of multiple processes or instances of your application. The system manages one process for your application and that process drives one or more scenes.

iOS 13 and Later

Exploring the New Project Template

To better understand the scene-based API, we need to explore Xcode's new project template. Fire up Xcode 11 or higher and create a new project by choosing the Single View App template from the iOS > Application section.

Setting Up the Project in Xcode 11

Name the project Windows, set User Interface to Storyboard, and leave the checkboxes at the bottom unchecked.

Setting Up the Project in Xcode 11

Every project created with Xcode 11 is scene-based by default. Start by opening AppDelegate.swift. The template for the AppDelegate class is much shorter than it used to be. Notice that the window property is missing. Remember that the application delegate is no longer responsible for creating and configuring the application window. This also means that it no longer has a reference to a window.

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    // MARK: - Application Life Cycle

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

    // MARK: - Scene Session Life Cycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    	
    }

}

The template contains the familiar application(_:didFinishLaunchingWithOptions:) method of the UIApplicationDelegate protocol. This hasn't changed. As the name implies, the application(_:didFinishLaunchingWithOptions:) method is invoked when the application has finished launching. The application delegate is notified that it can start preparing the application for use. The application. Not the user interface.

// MARK: - Application Life Cycle

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

Notice that the AppDelegate class includes two additional methods, application(_:configurationForConnecting:options:) and application(_:didDiscardSceneSessions:). We cover these methods later. What you need to know for now is that application(_:configurationForConnecting:options:) is invoked when a scene is created. The system asks which configuration it should use to create the scene. We cover scenes in more detail later. Remember for now that a scene is the same as a user interface instance of your application.

The application(_:didDiscardSceneSessions:) method is invoked when a scene is discarded, for example, when the user opens the application switcher and discards one or more scenes by swiping up. Notice that the second argument is a set of scene sessions. It is possible that the user discards multiple scenes at the same time.

// MARK: - Scene Session Life Cycle

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    
}

Let's now take a look at SceneDelegate.swift. The SceneDelegate class is new and automatically added to scene-based projects. Notice that the window property that was missing in the AppDelegate class has been moved to the SceneDelegate class. We explore the contents of the SceneDelegate class in more detail later.

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    // MARK: - Properties
    
    var window: UIWindow?

    // MARK: - Window Scene Delegate

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        
    }

    // MARK: -
    
    func sceneDidBecomeActive(_ scene: UIScene) {

    }

    func sceneWillResignActive(_ scene: UIScene) {

    }

    func sceneWillEnterForeground(_ scene: UIScene) {

    }

    func sceneDidEnterBackground(_ scene: UIScene) {

    }

}

Before we end this episode, I want to show you another important change. Open Info.plist. It contains a key with name Application Scene Manifest. The presence of this key indicates that the application adopts the scene-based API.

The Info.plist of a Scene-Based Project

The value is a dictionary with two keys, Enable Multiple Windows and Scene Configuration. The value of Enable Multiple Windows is set to NO by default. For applications that support multiple windows on iPad, the value is set to YES. We discuss the value of Scene Configuration later.

What's Next?

I hope this episode has given you a better idea of the changes introduced in iOS 13 and iPadOS 13. In the next episodes, we take a closer look at the scene-based API. What is a scene and what is a scene configuration? How is a scene created? I answer these questions in the next episode.