UIApplication Key Window Replacement

November 9, 2021 • 09:30 AM

With Split Screen on iPad, you may end up with two active windows of the same app. When I was working on building the menu system, I encountered the problem: how can I know which window scene is “active and current”, the same scene that invoked keyboard shortcuts?

In UIKit, the UIApplication class has a property named keyWindow. This property is marked as deprecated in iOS 13.0, accompanied by a message from Apple:

‘keyWindow’ was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes

So what’s its replacement for iOS 15?

TL;DR. As indicated by @saafo on Stackoverflow:

Although UIApplication.shared.keyWindow is deprecated after iOS 13.0 by Apple, it seems this attribute can help you find the active one when multiple scenes [are] in the foreground by using:

UIApplication.shared.keyWindow?.windowScene

So… there you have it. I have no idea why Apple marked keyWindow as deprecated. You should continue to use it until Apple finally replaces it with something functional, or when it becomes obsolete.

Below, I’ll discuss two attempts that did not work as of today on iPadOS 15.0.2.

Attempt 1: Using UIScene Activation State

A natual first attempt is to use the activationState property of each UIScene object:

if let currentScene = UIApplication.shared.connectedScenes
	.filter({$0.activationState == .foregroundActive})
	.first as? UIWindowScene 
{
	// do stuff...
}

This does not return one definite UIWindowScene instance. Both scenes on screen are returned by the filter method, and a random one is finally returned as the “current scene”. So this doesn’t work.

And this shouldn’t work indeed. ActivationState enum has four cases. The foregroundActive case is for when a scene is in the foreground and unobstructed; foregroundInactive is for when a scene is in the foreground but “not receiving events”, such as when it’s obstructed by full-screen calls on older iPadOS settings, or when user pulls down the lock screen or Control Center. Both UIWindowScene objects on the screenshot above should be foregroundActive by design.

Attempt 2: Check if scene contains a key window

Stackoverflow also pointed me to another answer by @pommy, and it’s (unfortunately) highly upvoted:

UIApplication
	.shared
	.connectedScenes
	.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
	.first { $0.isKeyWindow }

In the situation above in the screenshot, this method will return either of the two scene windows on screen, randomly. This is because the UI hierarchy is as such:

Application
+--- Scene 1
|	+--- Window 1.1 (key window)
|	|	+--- Root View Controller
|	+--- Window 1.2
|	+--- ... (more windows)
|
+--- Scene 2
	+--- Window 2.1 (key window)
	|	+--- Root View Controller
	+--- ... (more windows)

Each of the UIWindow instance can only evaluate if they are the key window in their containing UIScene. They cannot know much about the relationship of their scene in respect of the application.

Summary

In case you missed the TL;DR at the beginning—there is no replacement for keyWindow of the UIApplication class, and you should continue using it despite the deprecation. Workarounds discussed above fail to distinguish the “current” window scene when two are side by side using Split Screen on the iPad.

Maybe Apple eventually rolls out a isFocused property at the UIScene or UIWindow level. Who knows? As of now with iOS 15.0.2, your best bet is keep using the deprecated API.