簡體   English   中英

如何解決:“keyWindow”在 iOS 13.0 中已棄用

[英]How to resolve: 'keyWindow' was deprecated in iOS 13.0

我在 Cloud Kit 中使用 Core Data,因此必須在應用程序啟動期間檢查 iCloud 用戶狀態。 萬一出現問題,我想向用戶發出一個對話框,到目前為止,我使用UIApplication.shared.keyWindow?.rootViewController?.present(...)來做到這一點。

在 Xcode 11 beta 4 中,現在有一條新的棄用消息,告訴我:

'keyWindow' 在 iOS 13.0 中被棄用:不應該用於支持多個場景的應用程序,因為它在所有連接的場景中返回一個鍵 window

我應該如何呈現對話框?

編輯我在這里提出的建議在 iOS 15 中已被棄用。那么現在呢? 好吧,如果一個應用程序沒有自己的多個窗口,我認為公認的現代方式是獲取應用程序的第一個connectedScenes ,強制到 UIWindowScene 並獲取它的第一個窗口。 但這幾乎正是公認的答案所做的! 所以我的解決方法在這一點上感覺相當微弱。 但是,出於歷史原因,我會保留它。


公認的答案雖然巧妙,但可能過於詳盡。 您可以更簡單地獲得完全相同的結果:

UIApplication.shared.windows.filter {$0.isKeyWindow}.first

我還提醒說,不應該過分認真地對待棄用keyWindow 完整的警告信息如下:

'keyWindow' 在 iOS 13.0 中已棄用:不應用於支持多個場景的應用程序,因為它會在所有連接的場景中返回一個關鍵窗口

因此,如果您不支持 iPad 上的多個窗口,則不反對繼續使用keyWindow

這是我的解決方案:

let keyWindow = UIApplication.shared.connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .compactMap({$0 as? UIWindowScene})
        .first?.windows
        .filter({$0.isKeyWindow}).first

用法例如:

keyWindow?.endEditing(true)

iOS 15,兼容至 iOS 13

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

請注意, connectedScenes僅從 iOS 13 開始可用。如果您需要支持早期版本的 iOS,則必須將其放在if #available(iOS 13, *)語句中。

更長但更容易理解的變體:

UIApplication
.shared
.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }

iOS 13 和 14

以下歷史答案在 iOS 15 上仍然有效,但應替換,因為UIApplication.shared.windows已棄用。 感謝@matt 指出這一點!

原答案:

稍微改進了馬特的優秀答案,這更簡單,更短,更優雅:

UIApplication.shared.windows.first { $0.isKeyWindow }

這是一種向后兼容的檢測keyWindow的方法:

extension UIWindow {
    static var key: UIWindow? {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}

用法:

if let keyWindow = UIWindow.key {
    // Do something
}

通常使用

斯威夫特 5

UIApplication.shared.windows.filter {$0.isKeyWindow}.first

另外,在 UIViewController 中:

self.view.window

view.window是場景的當前窗口

世界開發者大會 2019: 在此處輸入圖像描述

關鍵窗口

  • 手動跟蹤窗口

對於 Objective-C 解決方案

+(UIWindow*)keyWindow
{
    UIWindow        *foundWindow = nil;
    NSArray         *windows = [[UIApplication sharedApplication]windows];
    for (UIWindow   *window in windows) {
        if (window.isKeyWindow) {
            foundWindow = window;
            break;
        }
    }
    return foundWindow;
}

一個UIApplication擴展:

extension UIApplication {

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

}

用法:

let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes

理想情況下,由於它已被棄用,我建議您將窗口存儲在 SceneDelegate 中。 但是,如果您確實需要臨時解決方法,則可以像這樣創建一個過濾器並檢索 keyWindow。

let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

如果你想在任何 ViewController 中使用它,那么你可以簡單地使用。

self.view.window

試試看:

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

也適用於 Objective-C 解決方案

@implementation UIWindow (iOS13)

+ (UIWindow*) keyWindow {
   NSPredicate *isKeyWindow = [NSPredicate predicateWithFormat:@"isKeyWindow == YES"];
   return [[[UIApplication sharedApplication] windows] filteredArrayUsingPredicate:isKeyWindow].firstObject;
}

@end

正如許多開發人員要求替換此棄用的Objective C代碼一樣。 您可以使用下面的代碼來使用 keyWindow。

+(UIWindow*)keyWindow {
    UIWindow        *windowRoot = nil;
    NSArray         *windows = [[UIApplication sharedApplication]windows];
    for (UIWindow   *window in windows) {
        if (window.isKeyWindow) {
            windowRoot = window;
            break;
        }
    }
    return windowRoot;
}

我在AppDelegate類中創建並添加了這個方法作為類方法,並以下面非常簡單的方式使用它。

[AppDelegate keyWindow];

不要忘記在 AppDelegate.h 類中添加此方法,如下所示。

+(UIWindow*)keyWindow;

靈感來自berni的回答

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

我已經在重復的提要上回答了這個問題,由於我在這里找不到提供盡可能多代碼的答案(已評論),所以這是我的貢獻:

(在 Xcode 13.2.1 上運行的 iOS 15.2 測試)

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)
    }
    
}

如果您想在鍵UIWindow中找到呈現的UIViewController ,這里是另一個您可能會發現有用的extension

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
    }
    
}

你可以把它放在任何你想要的地方,但我個人將它添加為UIViewControllerextension

這允許我添加更多有用的擴展,比如更容易呈現UIViewController的擴展,例如:

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)
        }
    }
    
}

支持 iOS 13 及更高版本。

要繼續使用與舊 iOS 版本UIApplication.shared.keyWindow類似的語法,請創建此擴展:

extension UIApplication {
    var mainKeyWindow: UIWindow? {
        get {
            if #available(iOS 13, *) {
                return connectedScenes
                    .flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
                    .first { $0.isKeyWindow }
            } else {
                return keyWindow
            }
        }
    }
}

用法

if let keyWindow = UIApplication.shared.mainKeyWindow {
    // Do Stuff
}
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 的代碼很好,但是當應用程序從后台返回時它不起作用。

這是我的代碼:

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;
}

如果您的應用尚未更新為采用基於場景的應用生命周期,另一種獲取活動窗口對象的簡單方法是通過UIApplicationDelegate

let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController

您可能知道,由於可能存在多個場景,因此不推薦使用密鑰窗口。 最方便的解決方案是提供一個currentWindow作為擴展,然后進行搜索和替換。

extension UIApplication {
    var currentWindow: UIWindow? {
        connectedScenes
            .compactMap { $0 as? UIWindowScene }
            .flatMap { $0.windows }
            .first { $0.isKeyWindow }
    }
}

我已經為單窗口應用程序實現了這個解決方案:

 var mainWindow: UIWindow? { if #available(iOS 13.0, *) { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { return windowScene.windows.first } } return UIApplication.shared.keyWindow } 

我遇到了同樣的問題。 我為視圖分配了一個newWindow ,並將其設置為[newWindow makeKeyAndVisible]; 使用完畢,設置[newWindow resignKeyWindow]; 然后嘗試通過[UIApplication sharedApplication].keyWindow直接顯示原始鍵窗口。

在 iOS 12 上一切正常,但在 iOS 13 上,原始鍵窗口無法正常顯示。 它顯示一個全白的屏幕。

我通過以下方式解決了這個問題:

UIWindow *mainWindow = nil;
if ( @available(iOS 13.0, *) ) {
   mainWindow = [UIApplication sharedApplication].windows.firstObject;
   [mainWindow makeKeyWindow];
} else {
    mainWindow = [UIApplication sharedApplication].keyWindow;
}

希望能幫助到你。

.foregroundActive場景為空時,我遇到了這個問題

所以這是我的解決方法

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
    }
}

抱歉,所有這些解決方案似乎都在試圖找到第一個關鍵窗口,但是如果兩個場景並排進行多任務處理,那么它們不會都是關鍵,現在獲得的幾率是 50/50正確的那一個?

我們不需要采取特定於應用程序的方法來確定哪個場景與正在運行的代碼相關嗎?

我認為該解決方案涉及構建代碼,以便當我們需要在 UI 上呈現某些內容時,代碼處理應該有權訪問用戶啟動操作或導航到的 UIViewController 或 UIView,因此我們將其用作基礎用於確定要呈現的內容。

如果您正在使用帶有“first_where”規則的 SwiftLint 並想消除交戰:

UIApplication.shared.windows.first(where: { $0.isKeyWindow })

我已經解決了:

let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first

對於 iOS 16,我使用了以下內容:

let keyWindow = UIApplication.shared.currentUIWindow()?.windowScene?.keyWindow

目標 C 解決方案:

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;
}

我的解決方案如下,適用於 iOS 15

let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM