Getting currently focused UIScene when multiple connected scenes are active in foreground

拜拜、爱过 提交于 2020-05-15 03:45:10

问题


I have a problem getting the current UIScene when multiple scenes are connected.

Specifically, my app (iOS 13) supports multiple windows on the iPad. When I have two windows side by side and I access the apps connected scenes (with UIApplication.shared.connectedScenes), all of them have the activationState set to .foregroundActive. But I am actually only interacting with one of them.

I found some code here on stackoverflow to retrieve the current key window (can't remember who it was that posted it) but as you can see it will always return one of the two windows.

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

Am I missing some method to determine the last window that was interacted with or are the multiple .foregroundActive states a bug?

Thanks!


回答1:


I found the solution to my problem by refactoring my code so that now I access the current UIScenes window through the window attribute of any view or view controller. This way I don't need the keyWindow anymore.

I think it's probably correct that bot windows open next to each other have the activationState .foregroundActive

In case anyone needs it, these are the two extensions that helped me get to to current window from anywhere in the app:

extension UIViewController {
  var window: UIWindow? {
    return view.window
  }
}

extension UIScene {
  var window: UIWindow? {
    return (delegate as? SceneDelegate)?.window
  }
}

But the problem still stands if you don't have access to a UIView, UIViewController or UIScene. For example inside a data model with no reference to a view or scene. Then I don't see a way the consistently accessing the correct window the user is interacting with.




回答2:


I use this extension in my project

// MARK: - Scene Delegate helpers

@available(iOS 13.0, *)
extension UIApplication {
    static var firstScene: UIScene? {
        UIApplication.shared.connectedScenes.first
    }

    static var firstSceneDelegate: SceneDelegate? {
        UIApplication.firstScene?.delegate as? SceneDelegate
    }

    static var firstWindowScene: UIWindowScene? {
        UIApplication.firstSceneDelegate?.windowScene
    }
}

// MARK: - Window Scene sugar

@available(iOS 13.0, *)
protocol WindowSceneDependable {
    var windowScene: UIWindowScene? { get }
    var window: UIWindow? { get }
}

@available(iOS 13.0, *)
extension WindowSceneDependable {
    var windowScene: UIWindowScene? {
        window?.windowScene
    }
}

@available(iOS 13.0, *)
extension UIScene: WindowSceneDependable { }

@available(iOS 13.0, *)
extension SceneDelegate: WindowSceneDependable { }

@available(iOS 13.0, *)
extension UIViewController: WindowSceneDependable { }

// MARK: - Window sugar

@available(iOS 13.0, *)
extension UIScene {
    var window: UIWindow? {
        (delegate as? SceneDelegate)?.window
    }
}

extension UIViewController {
    var window: UIWindow? {
        view.window
    }
}

And then I can do this when I have to present something:

func present(from navigation: UINavigationController) {
    self.navigation = navigation

    if #available(iOS 13.0, *) {
        guard let currentWindowScene = navigation.windowScene else { return }
        myPresenter.present(in: currentWindowScene)
    } else {
        myPresenter.present()
    }
}

Using this I can get a window from almost anywhere. But the thing is that it's not possible if I have no access to view-related objects at all. For example, in my auth service I have to use this bullshit:

func authorize() {
    if #available(iOS 13.0, *) {
        guard let currentWindowScene = UIApplication.firstWindowScene else { return }
        presenter.present(in: currentWindowScene)
    } else {
        presenter.present()
    }
}

This does not look correct for me, and it works only for the first windowScene. It's better not to do this just like I did, you have to elaborate on this solution.




回答3:


This solution gives you the last interacted-with UIScene without relying on anything but using a UIWindow subclass for all your scenes.

class Window: UIWindow
{
    static var lastActive: Window?

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
    {
        Self.lastActive = self
        return super.hitTest(point, with: event)
    }
}

Now you can access Window.lastActive and its associated windowScene (UIWindowScene/UIScene).



来源:https://stackoverflow.com/questions/57679284/getting-currently-focused-uiscene-when-multiple-connected-scenes-are-active-in-f

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!