On the Mac, it is very normal to have multiple windows of the same app open at the same time. For example, multiple text documents or multiple Xcode simulators each representing a different device. We are so used to this that we don’t even think about it we just expect it. With the introduction of iPadOS this now also became possible on the iPad. If it makes sense for your app this is definitely a feature you would want to implement.

In the previous blogpost we looked at how we can add support for the new scene delegate API to an existing app. In this blog post, we will go over how to make your app ready to support multiple windows

How users can open up new windows?

Before we dive into the technical implementation let us have a look at several different ways how users of your app can open up new windows.

Standard gestures and actions

The system provides a set of standard ways to open up a new window. Users can long-press on the app icon and drag it to the left or right side of the screen or they can just use the plus button in App Exposé.

Drag and Drop

Users can also drag and drop an item to the left or right side of the screen to create a new window. For example, in a recipe app, a user could drag and drop a recipe from the favorites list to open up the recipe details in a new window. This can be very powerful and while you won’t get this for free it does not require a lot of effort to implement it.

Explicit Action

You can also let users open up a new window through some more explicit action. For example, maybe for your application it makes sense to add an Open new window action to a context menu.

Enabling support for multiple windows

If you followed along with my previous blog or if you started a new project your app already implements the new scene API. However, this does not necessarily mean that your app already supports multiple windows. Whether your app supports multiple windows is defined in the scene manifest. An easy way to enable multiple window support is to go to the settings of your app target and enable the Supports multiple windows checkbox in the Deployment Info section of the General tab. Alternatively, you can open up the info.plist and set the UIApplicationSupportsMultipleScenes of your scenes manifest to true.

The scene Lifecycle

Every window is a combination of a scene and a scene session. The scene represents an instance of the UI and the session contains all information about the scene and manages it. It is important to know that when the system needs to free up memory it can get rid of a scene, but the session always stays alive. This called archiving the session.

An archived session does not have UI anymore but it does show a snapshot in the task switcher. When a user wants to open up an archived window you’ll need a way to restore the UI state. For this, you will need to make use of NSUserActivity, an object in which you persist the latest state of what the user was doing in your app. More on that in my next blog post.

Managing scenes programmatically

UIApplication maintains a list of all the sessions in the app and provides methods to control the windows in the app.

requestSceneSessionActivation(_:userActivity:options:errorHandler:)

This method allows you to open up a new scene (e.g. on a button tap) or an existing scene. An important thing to remember here is that you should only request to activate a session in response to user interaction. When you pass in an existing session in the sceneSession parameter the system will activate the scene belonging to that session. When you specify nil a new session will be created. Let’s have a look at how you would open up a session after the user tapped a button in your app.

First, you need to check if there is already an existing session open that fits your requirements. You do this as follows:

let existingSession = UIApplication.shared.openSessions.first { sceneSession -> Bool in
  // Find an existing session by checking the information in the  `stateRestorationActivity`.
}

You then need to create a user activity object which will be used to determine which type of scene to create if existingSession is nil. The last thing you need to do before requesting a new scene is set up the activation options by creating and setting up an instance of UIScene.ActivationRequestOptions:

let options = UIScene.ActivationRequestOptions()
options.requestingScene = view.window!.windowScene

The only thing that is left to call into the application to request activation of the scene:

UIApplication.shared.requestSceneSessionActivation(existingSession, userActivity: movieDetailActivity, options: options, errorHandler: { error in
    // TODO: - Handle error
})

requestSceneSessionDestruction:options:errorHandler:

You can close a function with the request scene session destruction function. By passing it an options object you can specify the type of animation the system should use to close the scene if it is currently in the foreground. Otherwise it will be closed without animation.

guard let scene = view.window?.windowScene else { return }
let options = UIWindowSceneDestructionRequestOptions()
options.windowDismissalAnimation = .commit
UIApplication.shared.requestSceneSessionDestruction(scene.session, options: options, errorHandler: { error in
    // TODO: - Handle error
})

requestSceneSessionRefresh(_:)

On some significant model updates you might want to refresh relevant scenes for example so that they can update their UI or snapshot in the task switcher. You do this by calling the requestSceneSessionRefresh(_:) on the application. This will update the UI and the snapshot in the task switcher, the state restoration activity, etc. It is important to know that the system does not always fulfill this request right away. Instead, when necessary it will fulfill it at a later point in time.

Supporting multiple different scenes

Every app has a primary window, which allows the user to do everything within your app. This is the main scene of the app. Depending on the type of app your building this can already be enough to deliver a great user experience. However, for some apps it makes sense to add the ability to open secondary windows. Secondary windows are represented by a different type of scene and provide more specific functionality e.g. in a recipe app you could specify a scene that lets the user input a new recipe. As the only purpose of such a scene would be to add a new recipe to the app you would close the scene once the user presses cancel or save.

You specify which scenes your app supports in the scene manifest under the in the Info.plist. There you specify a unique name to identify the scene, the name of the scene delegate class of the scene and the storyboard that contains the initial UI of the scene.

Conclusion

That’s it, as you can see it does not take a lot of work to get started with multiple windows. There’s a lot more to cover but you are now already familiar with the basics of it. Now it is time to try it out in your own project.

Contact me on Twitter @kairadiagne if you have any questions, comments or feedback.