7

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!

kota
  • 85
  • 9
freshking
  • 1,686
  • 15
  • 26

4 Answers4

2

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 both 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 to consistently accessing the correct window the user is interacting with.

Koen.
  • 21,087
  • 6
  • 75
  • 76
freshking
  • 1,686
  • 15
  • 26
  • 2
    Its very frustrating. In side by side both scene are foreground active. You can actually interact with both scene at the same time using two fingers. Checking if the window is key does not help because is not always the last window that was interacted with. – svn Dec 09 '19 at 15:39
1

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.

kikiwora
  • 1,514
  • 8
  • 7
0

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

Rivera
  • 10,182
  • 3
  • 49
  • 96
0

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 in the foreground by using:

UIApplication.shared.keyWindow?.windowScene
Saafo
  • 91
  • 1
  • 4