windows' was deprecated in iOS 15.0 creating Custom Alert [duplicate] - swift

I'm using Core Data with Cloud Kit, and have therefore to check the iCloud user status during application startup. In case of problems I want to issue a dialog to the user, and I do it using UIApplication.shared.keyWindow?.rootViewController?.present(...) up to now.
In Xcode 11 beta 4, there is now a new deprecation message, telling me:
'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
How shall I present the dialog instead?

Edit The suggestion I make here is deprecated in iOS 15. So now what? Well, if an app doesn't have multiple windows of its own, I presume the accepted modern way would be to get the first of the app's connectedScenes, coerce to a UIWindowScene, and take its first window. But that is almost exactly what the accepted answer does! So my workaround feels rather feeble at this point. However, I'll let it stand for historical reasons.
The accepted answer, while ingenious, might be overly elaborate. You can get exactly the same result much more simply:
UIApplication.shared.windows.filter {$0.isKeyWindow}.first
I would also caution that the deprecation of keyWindow should not be taken overly seriously. The full warning message reads:
'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 if you are not supporting multiple windows on iPad there is no objection to going ahead and continuing to use keyWindow.

iOS 16, compatible down to iOS 15
As this thread keeps getting traffic three years later, I want to share what I consider the most elegant solution with current functionality. It also works with SwiftUI.
UIApplication
.shared
.connectedScenes
.compactMap { ($0 as? UIWindowScene)?.keyWindow }
.first
iOS 15 and 16, compatible down to iOS 13
UIApplication
.shared
.connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }
Note that connectedScenes is available only since iOS 13. If you need to support earlier versions of iOS, you have to place this in an if #available(iOS 13, *) statement.
A variant that is longer, but easier to understand:
UIApplication
.shared
.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
iOS 13 and 14
The following historical answer is still valid on iOS 15, but should be replaced because UIApplication.shared.windows is deprecated. Thanks to #matt for pointing this out!
Original answer:
Improving slightly on matt's excellent answer, this is even simpler, shorter, and more elegant:
UIApplication.shared.windows.first { $0.isKeyWindow }

This is my solution:
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.compactMap({$0 as? UIWindowScene})
.first?.windows
.filter({$0.isKeyWindow}).first
Usage e.g.:
keyWindow?.endEditing(true)

Here is a backward-compatible way of detecting keyWindow:
extension UIWindow {
static var key: UIWindow? {
if #available(iOS 13, *) {
return UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
return UIApplication.shared.keyWindow
}
}
}
Usage:
if let keyWindow = UIWindow.key {
// Do something
}

Usually use
Swift 5
UIApplication.shared.windows.filter {$0.isKeyWindow}.first
In addition,in the UIViewController:
self.view.window
view.window is current window for scenes
WWDC 2019:
Key Windows
Track windows manually
Introducing Multiple Windows on iPad - WWDC 2019 - Videos - Apple Developer
Supporting Multiple Windows on iPad | Apple Developer Documentation

For an Objective-C solution
+ (UIWindow *)keyWindow
{
NSArray<UIWindow *> *windows = [[UIApplication sharedApplication] windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
return window;
}
}
return nil;
}

A UIApplication extension:
extension UIApplication {
/// The app's key window taking into consideration apps that support multiple scenes.
var keyWindowInConnectedScenes: UIWindow? {
return windows.first(where: { $0.isKeyWindow })
}
}
Usage:
let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes

Ideally, since it has been deprecated I would advice you to store the window in the SceneDelegate. However if you do want a temporary workaround, you can create a filter and retrieve the keyWindow just like this.
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

Supports iOS 13 and later.
To keep using similar syntax as the older iOS versions UIApplication.shared.keyWindow create this extension:
extension UIApplication {
var mainKeyWindow: UIWindow? {
get {
if #available(iOS 13, *) {
return connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }
} else {
return keyWindow
}
}
}
}
Usage
if let keyWindow = UIApplication.shared.mainKeyWindow {
// Do Stuff
}

If you want to use it in any ViewController then you can simply use.
self.view.window

(Tested with iOS 15.2 running on Xcode 13.2.1)
extension UIApplication {
var keyWindow: UIWindow? {
// Get connected scenes
return UIApplication.shared.connectedScenes
// Keep only active scenes, onscreen and visible to the user
.filter { $0.activationState == .foregroundActive }
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap({ $0 as? UIWindowScene })?.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
}
}
If you want to find the presented UIViewController in the key UIWindow , here is another extension you could find useful:
extension UIApplication {
var keyWindowPresentedController: UIViewController? {
var viewController = self.keyWindow?.rootViewController
// If root `UIViewController` is a `UITabBarController`
if let presentedController = viewController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
}
// Go deeper to find the last presented `UIViewController`
while let presentedController = viewController?.presentedViewController {
// If root `UIViewController` is a `UITabBarController`
if let presentedController = presentedController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
} else {
// Otherwise, go deeper
viewController = presentedController
}
}
return viewController
}
}
You can put this wherever you want, but I personally added it as an extension to UIViewController.
This allows me to add more useful extensions, like ones to present UIViewControllers more easily for example:
extension UIViewController {
func presentInKeyWindow(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindow?.rootViewController?
.present(self, animated: animated, completion: completion)
}
}
func presentInKeyWindowPresentedController(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindowPresentedController?
.present(self, animated: animated, completion: completion)
}
}
}

try with that:
UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)

As many of developers asking for Objective C code of this deprecation's replacement. You can use this below code to use the keyWindow.
+(UIWindow*)keyWindow {
UIWindow *windowRoot = nil;
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
windowRoot = window;
break;
}
}
return windowRoot;
}
I created and added this method in the AppDelegate class as a class method and use it with very simple way that is below.
[AppDelegate keyWindow];
Don't forget to add this method in AppDelegate.h class like below.
+(UIWindow*)keyWindow;

For an Objective-C solution too
#implementation UIWindow (iOS13)
+ (UIWindow*) keyWindow {
NSPredicate *isKeyWindow = [NSPredicate predicateWithFormat:#"isKeyWindow == YES"];
return [[[UIApplication sharedApplication] windows] filteredArrayUsingPredicate:isKeyWindow].firstObject;
}
#end

Inspired by the answer of berni
let keyWindow = Array(UIApplication.shared.connectedScenes)
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first(where: { $0.isKeyWindow })

I've solved with:
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first

As you probably know, the key window is deprecated because of possible multiple scenes. The most convenient solution is to provide a currentWindow as an extension, then search-and-replace.
extension UIApplication {
var currentWindow: UIWindow? {
connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
}
}

NSSet *connectedScenes = [UIApplication sharedApplication].connectedScenes;
for (UIScene *scene in connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[UIWindowScene class]]) {
UIWindowScene *windowScene = (UIWindowScene *)scene;
for (UIWindow *window in windowScene.windows) {
UIViewController *viewController = window.rootViewController;
// Get the instance of your view controller
if ([viewController isKindOfClass:[YOUR_VIEW_CONTROLLER class]]) {
// Your code here...
break;
}
}
}
}

Berni's code is nice but it doesn't work when the app comes back from background.
This is my code:
class var safeArea : UIEdgeInsets
{
if #available(iOS 13, *) {
var keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
// <FIX> the above code doesn't work if the app comes back from background!
if (keyWindow == nil) {
keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
}
return keyWindow?.safeAreaInsets ?? UIEdgeInsets()
}
else {
guard let keyWindow = UIApplication.shared.keyWindow else { return UIEdgeInsets() }
return keyWindow.safeAreaInsets
}
}

- (UIWindow *)mainWindow {
NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
for (UIWindow *window in frontToBackWindows) {
BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
BOOL windowIsVisible = !window.hidden && window.alpha > 0;
BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal);
BOOL windowKeyWindow = window.isKeyWindow;
if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) {
return window;
}
}
return nil;
}

I faced the issue when .foregroundActive scenes were empty
So here is my workaround
public extension UIWindow {
#objc
static var main: UIWindow {
// Here we sort all the scenes in order to work around the case
// when no .foregroundActive scenes available and we need to look through
// all connectedScenes in order to find the most suitable one
let connectedScenes = UIApplication.shared.connectedScenes
.sorted { lhs, rhs in
let lhs = lhs.activationState
let rhs = rhs.activationState
switch lhs {
case .foregroundActive:
return true
case .foregroundInactive:
return rhs == .background || rhs == .unattached
case .background:
return rhs == .unattached
case .unattached:
return false
#unknown default:
return false
}
}
.compactMap { $0 as? UIWindowScene }
guard connectedScenes.isEmpty == false else {
fatalError("Connected scenes is empty")
}
let mainWindow = connectedScenes
.flatMap { $0.windows }
.first(where: \.isKeyWindow)
guard let window = mainWindow else {
fatalError("Couldn't get main window")
}
return window
}
}

If your app has not been updated to adopt the Scene based app lifecycle, another simple way to get the active window object is via UIApplicationDelegate:
let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController

if you're using SwiftLint with 'first_where' rule and wanna to silence warring:
UIApplication.shared.windows.first(where: { $0.isKeyWindow })

An Objective C solution:
UIWindow *foundWindow = nil;
NSSet *scenes=[[UIApplication sharedApplication] connectedScenes];
NSArray *windows;
for(id aScene in scenes){ // it's an NSSet so you can't use the first object
windows=[aScene windows];
if([aScene activationState]==UISceneActivationStateForegroundActive)
break;
}
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
foundWindow = window;
break;
}
}
// and to find the parent viewController:
UIViewController* parentController = foundWindow.rootViewController;
while( parentController.presentedViewController &&
parentController != parentController.presentedViewController ){
parentController = parentController.presentedViewController;
}

My solution is the following, works in iOS 15
let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first

I alloc'ed a newWindow for a view, and set it [newWindow makeKeyAndVisible];
When finished using it, set it [newWindow resignKeyWindow];
and then try to show the original key-window directly by [UIApplication sharedApplication].keyWindow.
Everything is all right on iOS 12, but on iOS 13 the original key-window can't been normal shown. It shows a whole white screen.
I solved this problem by:
UIWindow *mainWindow = nil;
if ( #available(iOS 13.0, *) ) {
mainWindow = [UIApplication sharedApplication].windows.firstObject;
[mainWindow makeKeyWindow];
} else {
mainWindow = [UIApplication sharedApplication].keyWindow;
}

For iOS 16, I used the following:
let keyWindow = UIApplication.shared.currentUIWindow()?.windowScene?.keyWindow

Related

XCode Preview Error: "Replaced Accessor for 'keyWindow'"

Adding the some code to one of my files in XCode causes the following error to show when trying to run previews for that file:
Compiling failed: replaced accessor for 'keyWindow' occurs in multiple places.
The error only happens when the following code is used in the file:
extension UIApplication {
var keyWindow: UIWindow? {
return UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first(where: { $0 is UIWindowScene })
.flatMap({ $0 as? UIWindowScene })?.windows
.first(where: \.isKeyWindow)
}
var keyWindowPresentedController: UIViewController? {
var viewController = self.keyWindow?.rootViewController
if let presentedController = viewController as? UITabBarController {
viewController = presentedController.selectedViewController
}
while let presentedController = viewController?.presentedViewController {
if let presentedController = presentedController as? UITabBarController {
viewController = presentedController.selectedViewController
} else {
viewController = presentedController
}
}
return viewController
}
}
I know very little about UIKit, and this code was copy/pasted from online. Why is this code crashing my preview, and how can I fix it?
I'm running XCode 13.4.1 on macOS Monterey
UIApplication already has keyWindow property and it seems there is already other extension with same property in workspace, so just use different name for your, like
extension UIApplication {
var currentWindow: UIWindow? {
*or find which one another is and see if you can reuse it.

Issue Key.window was deprecated in iOS 13 [duplicate]

I'm using Core Data with Cloud Kit, and have therefore to check the iCloud user status during application startup. In case of problems I want to issue a dialog to the user, and I do it using UIApplication.shared.keyWindow?.rootViewController?.present(...) up to now.
In Xcode 11 beta 4, there is now a new deprecation message, telling me:
'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
How shall I present the dialog instead?
Edit The suggestion I make here is deprecated in iOS 15. So now what? Well, if an app doesn't have multiple windows of its own, I presume the accepted modern way would be to get the first of the app's connectedScenes, coerce to a UIWindowScene, and take its first window. But that is almost exactly what the accepted answer does! So my workaround feels rather feeble at this point. However, I'll let it stand for historical reasons.
The accepted answer, while ingenious, might be overly elaborate. You can get exactly the same result much more simply:
UIApplication.shared.windows.filter {$0.isKeyWindow}.first
I would also caution that the deprecation of keyWindow should not be taken overly seriously. The full warning message reads:
'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 if you are not supporting multiple windows on iPad there is no objection to going ahead and continuing to use keyWindow.
iOS 16, compatible down to iOS 15
As this thread keeps getting traffic three years later, I want to share what I consider the most elegant solution with current functionality. It also works with SwiftUI.
UIApplication
.shared
.connectedScenes
.compactMap { ($0 as? UIWindowScene)?.keyWindow }
.first
iOS 15 and 16, compatible down to iOS 13
UIApplication
.shared
.connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }
Note that connectedScenes is available only since iOS 13. If you need to support earlier versions of iOS, you have to place this in an if #available(iOS 13, *) statement.
A variant that is longer, but easier to understand:
UIApplication
.shared
.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
iOS 13 and 14
The following historical answer is still valid on iOS 15, but should be replaced because UIApplication.shared.windows is deprecated. Thanks to #matt for pointing this out!
Original answer:
Improving slightly on matt's excellent answer, this is even simpler, shorter, and more elegant:
UIApplication.shared.windows.first { $0.isKeyWindow }
This is my solution:
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.compactMap({$0 as? UIWindowScene})
.first?.windows
.filter({$0.isKeyWindow}).first
Usage e.g.:
keyWindow?.endEditing(true)
Here is a backward-compatible way of detecting keyWindow:
extension UIWindow {
static var key: UIWindow? {
if #available(iOS 13, *) {
return UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
return UIApplication.shared.keyWindow
}
}
}
Usage:
if let keyWindow = UIWindow.key {
// Do something
}
Usually use
Swift 5
UIApplication.shared.windows.filter {$0.isKeyWindow}.first
In addition,in the UIViewController:
self.view.window
view.window is current window for scenes
WWDC 2019:
Key Windows
Track windows manually
Introducing Multiple Windows on iPad - WWDC 2019 - Videos - Apple Developer
Supporting Multiple Windows on iPad | Apple Developer Documentation
For an Objective-C solution
+ (UIWindow *)keyWindow
{
NSArray<UIWindow *> *windows = [[UIApplication sharedApplication] windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
return window;
}
}
return nil;
}
A UIApplication extension:
extension UIApplication {
/// The app's key window taking into consideration apps that support multiple scenes.
var keyWindowInConnectedScenes: UIWindow? {
return windows.first(where: { $0.isKeyWindow })
}
}
Usage:
let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes
Ideally, since it has been deprecated I would advice you to store the window in the SceneDelegate. However if you do want a temporary workaround, you can create a filter and retrieve the keyWindow just like this.
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
Supports iOS 13 and later.
To keep using similar syntax as the older iOS versions UIApplication.shared.keyWindow create this extension:
extension UIApplication {
var mainKeyWindow: UIWindow? {
get {
if #available(iOS 13, *) {
return connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }
} else {
return keyWindow
}
}
}
}
Usage
if let keyWindow = UIApplication.shared.mainKeyWindow {
// Do Stuff
}
If you want to use it in any ViewController then you can simply use.
self.view.window
(Tested with iOS 15.2 running on Xcode 13.2.1)
extension UIApplication {
var keyWindow: UIWindow? {
// Get connected scenes
return UIApplication.shared.connectedScenes
// Keep only active scenes, onscreen and visible to the user
.filter { $0.activationState == .foregroundActive }
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap({ $0 as? UIWindowScene })?.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
}
}
If you want to find the presented UIViewController in the key UIWindow , here is another extension you could find useful:
extension UIApplication {
var keyWindowPresentedController: UIViewController? {
var viewController = self.keyWindow?.rootViewController
// If root `UIViewController` is a `UITabBarController`
if let presentedController = viewController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
}
// Go deeper to find the last presented `UIViewController`
while let presentedController = viewController?.presentedViewController {
// If root `UIViewController` is a `UITabBarController`
if let presentedController = presentedController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
} else {
// Otherwise, go deeper
viewController = presentedController
}
}
return viewController
}
}
You can put this wherever you want, but I personally added it as an extension to UIViewController.
This allows me to add more useful extensions, like ones to present UIViewControllers more easily for example:
extension UIViewController {
func presentInKeyWindow(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindow?.rootViewController?
.present(self, animated: animated, completion: completion)
}
}
func presentInKeyWindowPresentedController(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindowPresentedController?
.present(self, animated: animated, completion: completion)
}
}
}
try with that:
UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)
As many of developers asking for Objective C code of this deprecation's replacement. You can use this below code to use the keyWindow.
+(UIWindow*)keyWindow {
UIWindow *windowRoot = nil;
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
windowRoot = window;
break;
}
}
return windowRoot;
}
I created and added this method in the AppDelegate class as a class method and use it with very simple way that is below.
[AppDelegate keyWindow];
Don't forget to add this method in AppDelegate.h class like below.
+(UIWindow*)keyWindow;
For an Objective-C solution too
#implementation UIWindow (iOS13)
+ (UIWindow*) keyWindow {
NSPredicate *isKeyWindow = [NSPredicate predicateWithFormat:#"isKeyWindow == YES"];
return [[[UIApplication sharedApplication] windows] filteredArrayUsingPredicate:isKeyWindow].firstObject;
}
#end
Inspired by the answer of berni
let keyWindow = Array(UIApplication.shared.connectedScenes)
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first(where: { $0.isKeyWindow })
I've solved with:
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
As you probably know, the key window is deprecated because of possible multiple scenes. The most convenient solution is to provide a currentWindow as an extension, then search-and-replace.
extension UIApplication {
var currentWindow: UIWindow? {
connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
}
}
NSSet *connectedScenes = [UIApplication sharedApplication].connectedScenes;
for (UIScene *scene in connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[UIWindowScene class]]) {
UIWindowScene *windowScene = (UIWindowScene *)scene;
for (UIWindow *window in windowScene.windows) {
UIViewController *viewController = window.rootViewController;
// Get the instance of your view controller
if ([viewController isKindOfClass:[YOUR_VIEW_CONTROLLER class]]) {
// Your code here...
break;
}
}
}
}
Berni's code is nice but it doesn't work when the app comes back from background.
This is my code:
class var safeArea : UIEdgeInsets
{
if #available(iOS 13, *) {
var keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
// <FIX> the above code doesn't work if the app comes back from background!
if (keyWindow == nil) {
keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
}
return keyWindow?.safeAreaInsets ?? UIEdgeInsets()
}
else {
guard let keyWindow = UIApplication.shared.keyWindow else { return UIEdgeInsets() }
return keyWindow.safeAreaInsets
}
}
- (UIWindow *)mainWindow {
NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
for (UIWindow *window in frontToBackWindows) {
BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
BOOL windowIsVisible = !window.hidden && window.alpha > 0;
BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal);
BOOL windowKeyWindow = window.isKeyWindow;
if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) {
return window;
}
}
return nil;
}
I faced the issue when .foregroundActive scenes were empty
So here is my workaround
public extension UIWindow {
#objc
static var main: UIWindow {
// Here we sort all the scenes in order to work around the case
// when no .foregroundActive scenes available and we need to look through
// all connectedScenes in order to find the most suitable one
let connectedScenes = UIApplication.shared.connectedScenes
.sorted { lhs, rhs in
let lhs = lhs.activationState
let rhs = rhs.activationState
switch lhs {
case .foregroundActive:
return true
case .foregroundInactive:
return rhs == .background || rhs == .unattached
case .background:
return rhs == .unattached
case .unattached:
return false
#unknown default:
return false
}
}
.compactMap { $0 as? UIWindowScene }
guard connectedScenes.isEmpty == false else {
fatalError("Connected scenes is empty")
}
let mainWindow = connectedScenes
.flatMap { $0.windows }
.first(where: \.isKeyWindow)
guard let window = mainWindow else {
fatalError("Couldn't get main window")
}
return window
}
}
If your app has not been updated to adopt the Scene based app lifecycle, another simple way to get the active window object is via UIApplicationDelegate:
let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController
if you're using SwiftLint with 'first_where' rule and wanna to silence warring:
UIApplication.shared.windows.first(where: { $0.isKeyWindow })
An Objective C solution:
UIWindow *foundWindow = nil;
NSSet *scenes=[[UIApplication sharedApplication] connectedScenes];
NSArray *windows;
for(id aScene in scenes){ // it's an NSSet so you can't use the first object
windows=[aScene windows];
if([aScene activationState]==UISceneActivationStateForegroundActive)
break;
}
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
foundWindow = window;
break;
}
}
// and to find the parent viewController:
UIViewController* parentController = foundWindow.rootViewController;
while( parentController.presentedViewController &&
parentController != parentController.presentedViewController ){
parentController = parentController.presentedViewController;
}
My solution is the following, works in iOS 15
let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first
I alloc'ed a newWindow for a view, and set it [newWindow makeKeyAndVisible];
When finished using it, set it [newWindow resignKeyWindow];
and then try to show the original key-window directly by [UIApplication sharedApplication].keyWindow.
Everything is all right on iOS 12, but on iOS 13 the original key-window can't been normal shown. It shows a whole white screen.
I solved this problem by:
UIWindow *mainWindow = nil;
if ( #available(iOS 13.0, *) ) {
mainWindow = [UIApplication sharedApplication].windows.firstObject;
[mainWindow makeKeyWindow];
} else {
mainWindow = [UIApplication sharedApplication].keyWindow;
}
For iOS 16, I used the following:
let keyWindow = UIApplication.shared.currentUIWindow()?.windowScene?.keyWindow

How to disabled fullscreen button on MacCatalyst

Currently, I have this to stop resizing window:
#if targetEnvironment(macCatalyst)
windowScene.sizeRestrictions?.minimumSize = CGSize(width: 480, height: 900)
windowScene.sizeRestrictions?.maximumSize = CGSize(width: 480, height: 900)
#endif
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
but the fullscreen button makes it full screen anyway.
Here is another approach that does not need Objective-C, selectors, or asynchronous calls. It also does not need target macros, iOS will simply skip if let NSApplication. Paste it into your view controller that appears first. Note that this disables the green full-screen button on all your windows. If you want to differentiate, use ideas from Asperi's Swift part.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
func bitSet(_ bits: [Int]) -> UInt {
return bits.reduce(0) { $0 | (1 << $1) }
}
func property(_ property: String, object: NSObject, set: [Int], clear: [Int]) {
if let value = object.value(forKey: property) as? UInt {
object.setValue((value & ~bitSet(clear)) | bitSet(set), forKey: property)
}
}
// disable full-screen button
if let NSApplication = NSClassFromString("NSApplication") as? NSObject.Type,
let sharedApplication = NSApplication.value(forKeyPath: "sharedApplication") as? NSObject,
let windows = sharedApplication.value(forKeyPath: "windows") as? [NSObject]
{
for window in windows {
let resizable = 3
property("styleMask", object: window, set: [], clear: [resizable])
let fullScreenPrimary = 7
let fullScreenAuxiliary = 8
let fullScreenNone = 9
property("collectionBehavior", object: window, set: [fullScreenNone], clear: [fullScreenPrimary, fullScreenAuxiliary])
}
}
}
It's a bit complicated but possible. Here is an approach (I dropped all target macros to simplify post).
Result:
Code:
// on next event after UIWindow has made key it is possible to find NSWindow in runtime
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
...
window.makeKeyAndVisible()
DispatchQueue.main.async { // < wait for NSWindow available
SilentBridge.disableCloseButton(for: self.nsWindow(from: window))
}
}
// added helper function to SceneDelegate to find NSWindow
func nsWindow(from window: UIWindow) -> NSObject? {
guard let nsWindows = NSClassFromString("NSApplication")?.value(forKeyPath: "sharedApplication.windows") as? [NSObject] else { return nil }
for nsWindow in nsWindows {
let uiWindows = nsWindow.value(forKeyPath: "uiWindows") as? [UIWindow] ?? []
if uiWindows.contains(window) {
return nsWindow
}
}
return nil
}
Objective-C part is preferred (it's just simpler to work with non-declared selectors). Add new Objective-C class via Xcode template and confirm creating bridge. Afterwards it is needed to add below class header file in generated *-Bridging-Header.h and all should work.
// SilentBridge.h
#import Foundation;
#interface SilentBridge : NSObject
+ (void)disableCloseButtonFor:(NSObject * _Nullable)window;
#end
// SilentBridge.m
#import "SilentBridge.h"
#import Foundation;
// Forward declarations to allow direct calls in below method
#interface NSObject(SilentBridge)
- (id)standardWindowButton:(NSInteger)value;
- (void)setEnabled:(BOOL)flag;
#end
#implementation SilentBridge
+ (void)disableCloseButtonFor:(NSObject *)window {
if ([window respondsToSelector:#selector(standardWindowButton:)]) {
id closeButton = [window standardWindowButton:2];
if ([closeButton respondsToSelector:#selector(setEnabled:)]) {
[closeButton setEnabled:NO];
}
}
}
#end

How to solve 'keyWindow' was depreciated in iOS 13 (AutoLayout) [duplicate]

I'm using Core Data with Cloud Kit, and have therefore to check the iCloud user status during application startup. In case of problems I want to issue a dialog to the user, and I do it using UIApplication.shared.keyWindow?.rootViewController?.present(...) up to now.
In Xcode 11 beta 4, there is now a new deprecation message, telling me:
'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
How shall I present the dialog instead?
Edit The suggestion I make here is deprecated in iOS 15. So now what? Well, if an app doesn't have multiple windows of its own, I presume the accepted modern way would be to get the first of the app's connectedScenes, coerce to a UIWindowScene, and take its first window. But that is almost exactly what the accepted answer does! So my workaround feels rather feeble at this point. However, I'll let it stand for historical reasons.
The accepted answer, while ingenious, might be overly elaborate. You can get exactly the same result much more simply:
UIApplication.shared.windows.filter {$0.isKeyWindow}.first
I would also caution that the deprecation of keyWindow should not be taken overly seriously. The full warning message reads:
'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 if you are not supporting multiple windows on iPad there is no objection to going ahead and continuing to use keyWindow.
iOS 16, compatible down to iOS 15
As this thread keeps getting traffic three years later, I want to share what I consider the most elegant solution with current functionality. It also works with SwiftUI.
UIApplication
.shared
.connectedScenes
.compactMap { ($0 as? UIWindowScene)?.keyWindow }
.first
iOS 15 and 16, compatible down to iOS 13
UIApplication
.shared
.connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }
Note that connectedScenes is available only since iOS 13. If you need to support earlier versions of iOS, you have to place this in an if #available(iOS 13, *) statement.
A variant that is longer, but easier to understand:
UIApplication
.shared
.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
iOS 13 and 14
The following historical answer is still valid on iOS 15, but should be replaced because UIApplication.shared.windows is deprecated. Thanks to #matt for pointing this out!
Original answer:
Improving slightly on matt's excellent answer, this is even simpler, shorter, and more elegant:
UIApplication.shared.windows.first { $0.isKeyWindow }
This is my solution:
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.compactMap({$0 as? UIWindowScene})
.first?.windows
.filter({$0.isKeyWindow}).first
Usage e.g.:
keyWindow?.endEditing(true)
Here is a backward-compatible way of detecting keyWindow:
extension UIWindow {
static var key: UIWindow? {
if #available(iOS 13, *) {
return UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
return UIApplication.shared.keyWindow
}
}
}
Usage:
if let keyWindow = UIWindow.key {
// Do something
}
Usually use
Swift 5
UIApplication.shared.windows.filter {$0.isKeyWindow}.first
In addition,in the UIViewController:
self.view.window
view.window is current window for scenes
WWDC 2019:
Key Windows
Track windows manually
Introducing Multiple Windows on iPad - WWDC 2019 - Videos - Apple Developer
Supporting Multiple Windows on iPad | Apple Developer Documentation
For an Objective-C solution
+ (UIWindow *)keyWindow
{
NSArray<UIWindow *> *windows = [[UIApplication sharedApplication] windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
return window;
}
}
return nil;
}
A UIApplication extension:
extension UIApplication {
/// The app's key window taking into consideration apps that support multiple scenes.
var keyWindowInConnectedScenes: UIWindow? {
return windows.first(where: { $0.isKeyWindow })
}
}
Usage:
let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes
Ideally, since it has been deprecated I would advice you to store the window in the SceneDelegate. However if you do want a temporary workaround, you can create a filter and retrieve the keyWindow just like this.
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
Supports iOS 13 and later.
To keep using similar syntax as the older iOS versions UIApplication.shared.keyWindow create this extension:
extension UIApplication {
var mainKeyWindow: UIWindow? {
get {
if #available(iOS 13, *) {
return connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }
} else {
return keyWindow
}
}
}
}
Usage
if let keyWindow = UIApplication.shared.mainKeyWindow {
// Do Stuff
}
If you want to use it in any ViewController then you can simply use.
self.view.window
(Tested with iOS 15.2 running on Xcode 13.2.1)
extension UIApplication {
var keyWindow: UIWindow? {
// Get connected scenes
return UIApplication.shared.connectedScenes
// Keep only active scenes, onscreen and visible to the user
.filter { $0.activationState == .foregroundActive }
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap({ $0 as? UIWindowScene })?.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
}
}
If you want to find the presented UIViewController in the key UIWindow , here is another extension you could find useful:
extension UIApplication {
var keyWindowPresentedController: UIViewController? {
var viewController = self.keyWindow?.rootViewController
// If root `UIViewController` is a `UITabBarController`
if let presentedController = viewController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
}
// Go deeper to find the last presented `UIViewController`
while let presentedController = viewController?.presentedViewController {
// If root `UIViewController` is a `UITabBarController`
if let presentedController = presentedController as? UITabBarController {
// Move to selected `UIViewController`
viewController = presentedController.selectedViewController
} else {
// Otherwise, go deeper
viewController = presentedController
}
}
return viewController
}
}
You can put this wherever you want, but I personally added it as an extension to UIViewController.
This allows me to add more useful extensions, like ones to present UIViewControllers more easily for example:
extension UIViewController {
func presentInKeyWindow(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindow?.rootViewController?
.present(self, animated: animated, completion: completion)
}
}
func presentInKeyWindowPresentedController(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
UIApplication.shared.keyWindowPresentedController?
.present(self, animated: animated, completion: completion)
}
}
}
try with that:
UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)
As many of developers asking for Objective C code of this deprecation's replacement. You can use this below code to use the keyWindow.
+(UIWindow*)keyWindow {
UIWindow *windowRoot = nil;
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
windowRoot = window;
break;
}
}
return windowRoot;
}
I created and added this method in the AppDelegate class as a class method and use it with very simple way that is below.
[AppDelegate keyWindow];
Don't forget to add this method in AppDelegate.h class like below.
+(UIWindow*)keyWindow;
For an Objective-C solution too
#implementation UIWindow (iOS13)
+ (UIWindow*) keyWindow {
NSPredicate *isKeyWindow = [NSPredicate predicateWithFormat:#"isKeyWindow == YES"];
return [[[UIApplication sharedApplication] windows] filteredArrayUsingPredicate:isKeyWindow].firstObject;
}
#end
Inspired by the answer of berni
let keyWindow = Array(UIApplication.shared.connectedScenes)
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first(where: { $0.isKeyWindow })
I've solved with:
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
As you probably know, the key window is deprecated because of possible multiple scenes. The most convenient solution is to provide a currentWindow as an extension, then search-and-replace.
extension UIApplication {
var currentWindow: UIWindow? {
connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }
}
}
NSSet *connectedScenes = [UIApplication sharedApplication].connectedScenes;
for (UIScene *scene in connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[UIWindowScene class]]) {
UIWindowScene *windowScene = (UIWindowScene *)scene;
for (UIWindow *window in windowScene.windows) {
UIViewController *viewController = window.rootViewController;
// Get the instance of your view controller
if ([viewController isKindOfClass:[YOUR_VIEW_CONTROLLER class]]) {
// Your code here...
break;
}
}
}
}
Berni's code is nice but it doesn't work when the app comes back from background.
This is my code:
class var safeArea : UIEdgeInsets
{
if #available(iOS 13, *) {
var keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
// <FIX> the above code doesn't work if the app comes back from background!
if (keyWindow == nil) {
keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
}
return keyWindow?.safeAreaInsets ?? UIEdgeInsets()
}
else {
guard let keyWindow = UIApplication.shared.keyWindow else { return UIEdgeInsets() }
return keyWindow.safeAreaInsets
}
}
- (UIWindow *)mainWindow {
NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
for (UIWindow *window in frontToBackWindows) {
BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
BOOL windowIsVisible = !window.hidden && window.alpha > 0;
BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal);
BOOL windowKeyWindow = window.isKeyWindow;
if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) {
return window;
}
}
return nil;
}
I faced the issue when .foregroundActive scenes were empty
So here is my workaround
public extension UIWindow {
#objc
static var main: UIWindow {
// Here we sort all the scenes in order to work around the case
// when no .foregroundActive scenes available and we need to look through
// all connectedScenes in order to find the most suitable one
let connectedScenes = UIApplication.shared.connectedScenes
.sorted { lhs, rhs in
let lhs = lhs.activationState
let rhs = rhs.activationState
switch lhs {
case .foregroundActive:
return true
case .foregroundInactive:
return rhs == .background || rhs == .unattached
case .background:
return rhs == .unattached
case .unattached:
return false
#unknown default:
return false
}
}
.compactMap { $0 as? UIWindowScene }
guard connectedScenes.isEmpty == false else {
fatalError("Connected scenes is empty")
}
let mainWindow = connectedScenes
.flatMap { $0.windows }
.first(where: \.isKeyWindow)
guard let window = mainWindow else {
fatalError("Couldn't get main window")
}
return window
}
}
If your app has not been updated to adopt the Scene based app lifecycle, another simple way to get the active window object is via UIApplicationDelegate:
let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController
if you're using SwiftLint with 'first_where' rule and wanna to silence warring:
UIApplication.shared.windows.first(where: { $0.isKeyWindow })
An Objective C solution:
UIWindow *foundWindow = nil;
NSSet *scenes=[[UIApplication sharedApplication] connectedScenes];
NSArray *windows;
for(id aScene in scenes){ // it's an NSSet so you can't use the first object
windows=[aScene windows];
if([aScene activationState]==UISceneActivationStateForegroundActive)
break;
}
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
foundWindow = window;
break;
}
}
// and to find the parent viewController:
UIViewController* parentController = foundWindow.rootViewController;
while( parentController.presentedViewController &&
parentController != parentController.presentedViewController ){
parentController = parentController.presentedViewController;
}
My solution is the following, works in iOS 15
let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first
I alloc'ed a newWindow for a view, and set it [newWindow makeKeyAndVisible];
When finished using it, set it [newWindow resignKeyWindow];
and then try to show the original key-window directly by [UIApplication sharedApplication].keyWindow.
Everything is all right on iOS 12, but on iOS 13 the original key-window can't been normal shown. It shows a whole white screen.
I solved this problem by:
UIWindow *mainWindow = nil;
if ( #available(iOS 13.0, *) ) {
mainWindow = [UIApplication sharedApplication].windows.firstObject;
[mainWindow makeKeyWindow];
} else {
mainWindow = [UIApplication sharedApplication].keyWindow;
}
For iOS 16, I used the following:
let keyWindow = UIApplication.shared.currentUIWindow()?.windowScene?.keyWindow

How to tell if there is a modal UIViewController presented?

Is there a way to tell whether there is a modal UIViewController presented already, say, before calling dismissModalViewControllerAnimated?
iOS 9, 8, 7, 6 & 5
There are just too many answers to this question, none covering all cases. Furthermore, despite what you find in the documentation, there are two alternatives to the now deprecated modalViewController:
If you need to know if you are modal:
BOOL modal = nil != [self presentingViewController];
If you need to know if you are covered by a modal:
BOOL hiddenByModal = nil != [self presentedViewController];
iOS6+ - use presentedViewController:
Since iOS 6, presentedViewController should be used instead as the modalViewController which has been deprecated
Use the property:
Deprecated - modalViewController:
The controller for the active modal view—that is, the view that is temporarily displayed on top of the view managed by the receiver. (read-only)
#property(nonatomic, readonly) UIViewController *modalViewController
I usually add a BOOL variable, called something like isModal, and I set it after initializing a viewcontroller but before calling presentModalViewController. Something like:
MyViewController *controller = [[MyViewController alloc] init];
controller.isModal = YES;
[self presentModalViewController:controller animated:YES];
And then, in MyViewController, before needing to dismiss, I just check:
if (isModal) { //dismiss modal }
after iOS 5 you should use:
if (self.presentingViewController != nil) {
[self dismissViewControllerAnimated:YES completion:^{
//has dismissViewControllerAnimated
}];
}
Edited to change iOS Version
I understand this has been a while but just wanted do add my 2 cents to this matter.
I was in need to identify if there was a modally presented ViewController when the app went to background in order to dismiss it first.
First I made an extension of UIWindow to return me the current ViewController:
extension UIWindow {
func getCurrentViewController() -> UIViewController? {
guard let rvc = self.rootViewController else {
return nil
}
if let pvc = rvc.presentedViewController {
return pvc
} else if let svc = rvc as? UISplitViewController, svc.viewControllers.count > 0 {
return svc.viewControllers.last!
} else if let nc = rvc as? UINavigationController, nc.viewControllers.count > 0 {
return nc.topViewController!
} else if let tbc = rvc as? UITabBarController {
if let svc = tbc.selectedViewController {
return svc
}
}
return rvc
}
}
Then I went into appDelegate and added a test on applicationDidEnterBackground():
func applicationDidEnterBackground(_ application: UIApplication) {
if let vc = self.window?.getCurrentViewController() {
if vc.presentingViewController != nil {
vc.dismiss(animated: false, completion: nil)
}
}
}
This solution is in Swift 3