Эквивалент UIApplication.shared.delegate для SceneDelegate xcode11?

Я определил свойство let в моем SceneDelegate. Я хотел бы, чтобы некоторые ViewController имели доступ к этому внутри сцены.

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

UIApplication.shared.delegate

затем приведение и указание имени свойства ...

Есть ли эквивалент для получения ссылки на SceneDelegate, в котором находится контроллер представления, из экземпляра UIViewController?


person zumzum    schedule 13.06.2019    source источник


Ответы (6)


Начиная с iOS 13, UIApplication имеет свойство connectedScenes, равное Set<UIScene>. У каждой из этих сцен есть delegate, который равен UISceneDelegate. Таким образом, вы могли получить доступ ко всем делегатам.

Сцена может управлять одним или несколькими окнами (UIWindow), и вы можете получить UIScene окна из его свойства windowScene.

Если вам нужен делегат сцены для определенного контроллера представления, обратите внимание на следующее. Из UIViewController вы можете получить его окно из вида. Из окна вы можете получить его сцену и, конечно, из сцены вы можете получить ее делегата.

Короче говоря, с помощью контроллера представления вы можете:

let mySceneDelegate = self.view.window.windowScene.delegate

Однако во многих случаях контроллер представления не имеет окна. Это происходит, когда контроллер представления представляет другой контроллер полноэкранного представления. Это может произойти, когда контроллер представления находится в контроллере навигации, а контроллер представления не является верхним, видимым контроллером представления.

Это требует другого подхода к поиску сцены контроллера представления. В конечном итоге вам нужно использовать комбинацию обхода цепочки респондентов и иерархии контроллеров представления, пока вы не найдете путь, ведущий к сцене.

Следующее расширение (может) предоставить вам UIScene из представления или контроллера представления. Когда у вас есть сцена, вы можете получить доступ к ее делегату.

Добавьте UIResponder + Scene.swift:

import UIKit

@available(iOS 13.0, *)
extension UIResponder {
    @objc var scene: UIScene? {
        return nil
    }
}

@available(iOS 13.0, *)
extension UIScene {
    @objc override var scene: UIScene? {
        return self
    }
}

@available(iOS 13.0, *)
extension UIView {
    @objc override var scene: UIScene? {
        if let window = self.window {
            return window.windowScene
        } else {
            return self.next?.scene
        }
    }
}

@available(iOS 13.0, *)
extension UIViewController {
    @objc override var scene: UIScene? {
        // Try walking the responder chain
        var res = self.next?.scene
        if (res == nil) {
            // That didn't work. Try asking my parent view controller
            res = self.parent?.scene
        }
        if (res == nil) {
            // That didn't work. Try asking my presenting view controller 
            res = self.presentingViewController?.scene
        }

        return res
    }
}

Его можно вызвать из любого представления или контроллера представления, чтобы получить его сцену. Но обратите внимание, что вы можете получить сцену из контроллера представления только после того, как viewDidAppear был вызван хотя бы один раз. Если вы попробуете раньше, тогда контроллер представления может еще не быть частью иерархии контроллеров представления.

Это будет работать, даже если окно представления контроллера представления равно нулю, пока контроллер представления является частью иерархии контроллеров представления и где-то выше по этой иерархии он прикреплен к окну.


Вот реализация расширения UIResponder на Objective-C:

UIResponder + Scene.h:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIResponder (Scene)

@property (nonatomic, readonly, nullable) UIScene *scene API_AVAILABLE(ios(13.0));

@end

NS_ASSUME_NONNULL_END

UIResponder + Scene.m:

#import "ViewController+Scene.h"

@implementation UIResponder (Scene)

- (UIScene *)scene {
    return nil;
}

@end

@implementation UIScene (Scene)

- (UIScene *)scene {
    return self;
}

@end

@implementation UIView (Scene)

- (UIScene *)scene {
    if (self.window) {
        return self.window.windowScene;
    } else {
        return self.nextResponder.scene;
    }
}

@end

@implementation UIViewController (Scene)

- (UIScene *)scene {
    UIScene *res = self.nextResponder.scene;
    if (!res) {
        res = self.parentViewController.scene;
    }
    if (!res) {
        res = self.presentingViewController.scene;
    }

    return res;
}

@end
person rmaddy    schedule 13.06.2019
comment
У меня нулевое значение для self.view.window ... как мне получить доступ к текущему окну текущей сцены? Как старый appDelegate.window - person Fabiosoft; 09.07.2019
comment
@Fabiosoft Я обновил этот ответ, который позволяет вам получить сцену контроллера представления в большем количестве условий, даже если окно равно нулю. - person rmaddy; 14.08.2019
comment
Что это за версия с SwiftUI? @rmaddy - person Carla Camargo; 11.11.2019

Я смог заставить его работать, используя это:

let scene = UIApplication.shared.connectedScenes.first
if let sd : SceneDelegate = (scene?.delegate as? SceneDelegate) {
    sd.blah()
}
person JoeGalind    schedule 27.10.2019
comment
У меня есть логика для обработки обратных вызовов OAuth в делегате сцены и закрытие, к которому я хотел получить доступ в ViewController при возникновении обратного вызова. Этот код отлично позволяет мне получить доступ к делегату сцены в ViewController для доступа к этому закрытию. +1 - person TheBrockEllis; 30.10.2019
comment
Это действительно только в том случае, если в вашем приложении есть одна сцена. Все преимущество вспомогательных сцен в том, что у вас может быть более одной сцены. Такой код не будет работать должным образом при наличии более одной сцены. - person rmaddy; 11.11.2019
comment
да. Конечно. Это ярлык для проекта с одной сценой. Вам придется пройти через массив connectedScenes, если вы хотите получить доступ к более чем одной сцене. - person JoeGalind; 15.11.2019
comment
Это очень хорошо сработало для интеграции Azure MSAL SDK. Спасибо за это, сэкономив нам время на кодирование, чтобы сделать видимым модальное окно аутентификации MSAL. Полностью просмотрел это свойство в документации Apple UIApplication. - person matthew; 14.01.2020
comment
Это неверно, вы подключаетесь первым UIScene, а не UIScene родительским элементом UIViewController. - person Pedro Paulo Amorim; 15.01.2020
comment
@PedroPauloAmorim: это ярлык для 90% проектов, в которых есть только один UIScene. Для быстрого достижения этой цели необходимы три строчки кода. - person JoeGalind; 07.02.2020
comment
Тем не менее, это неправильное решение, потому что вы получаете значение от глобального контроллера, а не от вашего текущего UIScene. - person Pedro Paulo Amorim; 10.02.2020

JoeGalind Спасибо, я тоже решил аналогичным образом.

// iOS13 or later
if #available(iOS 13.0, *) {
    let sceneDelegate = UIApplication.shared.connectedScenes
        .first!.delegate as! SceneDelegate
    sceneDelegate.window!.rootViewController = /* ViewController Instance */

// iOS12 or earlier
} else {
    // UIApplication.shared.keyWindow?.rootViewController
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    appDelegate.window!.rootViewController = /* ViewController Instance */
}
person rkamiya87    schedule 07.03.2020

Я использую это в своем MasterViewController:

- (void)viewDidLoad {
    [super viewDidLoad];

    SceneDelegate *sceneDelegate = (SceneDelegate *)self.parentViewController.view.window.windowScene.delegate;
}

self.view.window на данный момент равен нулю, поэтому мне пришлось связаться с родителем, представление которого уже было загружено и добавлено в окно.

Если вы используете контроллер с разделенным представлением, а делегат сцены является разделенным делегатом, вы можете вообще избежать окна / сцены и просто выполните:

SceneDelegate *sceneDelegate = (SceneDelegate *)self.splitViewController.delegate;

Я использую это в своем DetailViewController.

person malhal    schedule 08.04.2020

Вам не нужно ссылаться на SceneDelegate, если вы только пытаетесь найти окно:

(UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first

Вы должны использовать этот подход только в том случае, если ваше приложение будет иметь только одну сцену и одно окно.

person Sam Soffes    schedule 11.01.2021

Я сохраняю ссылку на SceneDelegate в свойстве static weak и инициализирую его в scene(:willConnectTo:options)

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    static weak var shared: SceneDelegate?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        Self.shared = self
    }
}

Доступ к более поздней сцене можно получить с помощью SceneDelegate.shared

person Kaunteya    schedule 12.04.2021