iPadOS 13 Multiple Window Support

AppDelegate takes a back seat. SceneDelegate takes over

iPadOS 13 was launched during WWDC 2019. Finally, the iPad has a separate OS.

Introduction of multiple window support in iPadOS is a game-changing move. It allows us to open multiple instances of an application at the same time.

This awesome feature is incredibly useful when it comes to viewing multiple messages, emails, or comparing notes, and map routes. It’ll make life easier for photo and video editors as well. I believe multiple window support will give rise to interesting multiplayer games really soon!

Our Goal for Today

  • Knowing how multi-window support changes the application’s lifecycle.

  • A bird’s eye view of the UIScene API.

  • Implementing multiple window support in two different ways — With user input and using drag and drop. We’ll be developing a photo editing based iOS and iPadOS Application which uses CIFilters on a spiderman image!

Without wasting any more time, let’s get started.

Enabling Multi-Window Support

It’s easy, just jump into the project navigator, general settings, and ensure that the support multiple window checkbox is enabled.

Once this is done, the multiple window support boolean property is set in the info.plist. Before we dig deep into the API changes and implementation, let's address the elephant in the room.

Multiple window support is not split-screen support

Split-screen support was introduced in iOS 9 to allow viewing different apps in one window, whereas multiple window support allows viewing multiple instances of a single app at a time.

Changes in AppDelegate and App Lifecycle

Multi-window support has brought major changes to the AppDelegate class. It is much lighter now. All the heavy lifting is done by the SceneDelegate class. If you peek into the AppDelegate in any iOS 13 Xcode project, you’ll see that it has very few methods.

UIApplicationDelegate is not notified when the application goes and comes from the background in iOS 13 and above.

A newly introduced protocol, UIWindowSceneDelegate, handles the notifications across multiple windows of an application.

The following properties of the UIApplication class are now deprecated:

  • statusBarStyle

  • statusBarHidden

  • statusBarOrientationopen(_:options:completionHandler:)

  • keyWindows

Thanks to multi-window support, windows are now scenes! So starting iPadOS 13, everything you see in the app switcher is a separate scene.

UIScene API: A Bird’s Eye View

Multiple window support uses UIScene API under the hood. The two most essential classes of this API are:

  • UIWindowScene — This is responsible for managing multiple windows of an application.

  • UISceneSession — This represents a persisted state of the scene. Multiple UIScenes store specific information there, such as role and user info with the scene session.

NSUserActivity is used to capture the state of a scene. This state is used to restore a previously used scene or create a new scene with the current viewing content.

UIWindowSceneDelegate is implemented by the SceneDelegate . This protocol is the new entry to the application as it holds the references to the UIWindow .

UIWindow now holds a reference to the UIWindowScene. If you’re using storyboards, the UIWindow property would automatically be attached to the scene.

Not using storyboards? You need to manually attach the UIWindow to the scene in the SceneDelegate class as shown below:

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

If the above stuff didn’t make too much sense, the hands-on implementation in the next section will surely give more clarity. Let’s dig in!

Note: Never use multiple window support for extending functionality across scenes. Or by showing the output of one scene in another. Each scene should have the full functionality of the application.

Implementation

We’ll be creating a photo editing based application that allows viewing images with different filters across different scenes. It's handy when you need to determine which filter looks best on the image!

Laying the UI

The following snippet adds a UIImageView to the screen programmatically:

let VCActivityType = "VCKey"
func setupImageView(){
photo = UIImageView(frame: .zero)
photo?.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(photo!)
NSLayoutConstraint.activate([
photo!.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: marginConstant),
photo!.bottomAnchor.constraint(equalTo: self.stackView!.topAnchor, constant: -marginConstant),
photo!.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: marginConstant),
photo!.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -marginConstant),
])
}

Creating a New Scene on User Click

To create a new scene of the application, add the following code on a button press:

let activity = NSUserActivity(activityType: VCActivityType)
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil, errorHandler: nil)

requestSceneSessionActivation is responsible for activating an existing scene or creating a new scene.

Here’s a screengrab from the iPad application.

This was straightforward. Now, let’s hop onto the drag and drop mechanism and see what needs to be done there.

Creating a New Scene Using Drag and Drop

We can make use of the Drag and Drop API by passing the NSUserActivity inside the DragItem’s NSItemProvider.

When you drag a UI component, the system automatically provides drop points (typically at the edges of the screen). Dropping at those points leads to the creation of a new scene.

To create a new scene using drag and drop of the UIImageView, we must take care of three things:

1. Conformation to the UIDragInteractionDelegate

We need to set up the drag interaction on the UIImageView.

photo?.isUserInteractionEnabled = true
photo?.addInteraction(UIDragInteraction(delegate: self))

2. Implementing the drag function

In the below snippet, we pass the imageView to the NSItemProvider and register the NSUserActivity with it.

extension ViewController : UIDragInteractionDelegate{
func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
if let imageView = interaction.view as? UIImageView {
guard let image = imageView.image else { return [] }
let provider = NSItemProvider(object: image)
let userActivity = NSUserActivity(activityType: VCActivityType)
provider.registerObject(userActivity, visibility: .all)
let item = UIDragItem(itemProvider: provider)
return [item]
}
return []
}
}

3. Adding the ActivityType in the Info.plist

Our iPad application is now ready for multi-window support via dragging.

We’ve set CIFilters on the images to compare the same image across different filters.

Multi-window support is compatible with Mac Catalyst as well.

Conclusion

So, that’s it! We saw how AppDelegate takes a back seat and lets SceneDelegate do the primary weight lifting by handling most of the lifecycle events.

Finally, we implemented multi-window support and saw our friendly neighborhood in different shades in different scenes.

The full source code of this article is available on GitHub.

In the next parts, we’ll deal with state restoration and syncing data across multiple windows in iPadOS.

That’s it for this one. I hope you enjoyed it.