Получение текущей фокусированной UIScene, когда на переднем плане активны несколько связанных сцен

У меня проблема с получением тока UIScene когда несколько сцен связаны.

В частности, мое приложение (iOS 13) поддерживает несколько окон на iPad. Когда у меня есть два окна рядом, и я получаю доступ к приложениям, связанным сценами (с UIApplication.shared.connectedScenes), все они имеют activationState установлен в .foregroundActive, Но на самом деле я взаимодействую только с одним из них.

Я нашел некоторый код здесь в stackru для извлечения текущего ключевого окна (не могу вспомнить, кто это опубликовал), но, как вы можете видеть, оно всегда будет возвращать одно из двух окон.

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

Я пропустил какой-то метод, чтобы определить последнее окно, с которым взаимодействовали, или несколько .foregroundActive констатирует ошибку?

Спасибо!

4 ответа

Несмотря на то что UIApplication.shared.keyWindowустарел после iOS 13.0 Apple, кажется, этот атрибут может помочь вам найти активный, когда несколько сцен на переднем плане с помощью:

      UIApplication.shared.keyWindow?.windowScene

Я нашел решение своей проблемы путем рефакторинга своего кода, чтобы теперь я получил доступ к текущему UISceneокно через window атрибут любого представления или контроллера представления. Таким образом, мне не нужно keyWindow больше.

Я думаю, что это правильно, что окна ботов, открытые рядом друг с другом, имеют activationState.foregroundActive

В случае, если кому-то это нужно, вот два расширения, которые помогли мне добраться до текущего окна из любой точки приложения:

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

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

Но проблема остается, если у вас нет доступа к UIView, UIViewController или же UIScene, Например, внутри модели данных без ссылки на представление или сцену. Тогда я не вижу способа постоянного доступа к правильному окну, с которым взаимодействует пользователь.

Я использую это расширение в своем проекте

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

И тогда я могу это сделать, когда мне нужно что-то представить:

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

Используя это, я могу получить окно практически из любого места. Но дело в том, что это невозможно, если у меня вообще нет доступа к объектам, связанным с просмотром. Например, в моей службе авторизации я должен использовать эту чушь:

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

Мне это не кажется правильным и работает только для первого windowScene. Лучше не делать этого, как я, вы должны уточнить это решение.

Это решение дает вам последнее взаимодействие с UIScene не полагаясь ни на что, кроме использования UIWindow подкласс для всех ваших сцен.

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

Теперь вы можете получить доступ Window.lastActive и связанные с ним windowScene (UIWindowScene/UIScene).

Другие вопросы по тегам