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

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

В UIKit я мог получить доступ к свойствам App Delegate, как это:

UIApplication.shared.delegate

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

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

9 ответов

Решение

Начиная с 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 был вызван хотя бы один раз. Если вы попробуете что-либо раньше, то контроллер представления может еще не быть частью иерархии контроллера представления.

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


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

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

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

let scene = UIApplication.shared.connectedScenes.first
if let sd : SceneDelegate = (scene?.delegate as? SceneDelegate) {
    sd.blah()
}

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

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

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

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

iOS 13+

Сцены представляют собой экземпляры пользовательского интерфейса приложения, и каждая сцена поддерживает собственное, полностью независимое состояние. И само приложение, которое никогда не может быть более чем одним экземпляром, поддерживает ссылки на все эти связанные сцены. Поэтому просто сохраните ссылку на саму сцену внутри сцены, прежде чем она будет подключена к окну, а затем найдите эту сцену в наборе связанных сцен внутри .

      // Create a globally-accessible variable of some kind (global variable,
// static property, property of a singleton, etc.) that references
// the scene of this current "UI instance".
var currentScene: UIScene?

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let scene = scene as? UIWindowScene else {
            return
        }
        currentScene = scene // Record the reference when the scene is born.
    }
    
    func borat() {
        print("great success")
    }
}

// As a convenience, I've extended UIViewController to perform the task
// of finding the delegate so that it can be gotten from any view
// controller in the app.
extension UIViewController {
    var sceneDelegate: SceneDelegate? {
        for scene in UIApplication.shared.connectedScenes {
            if scene == currentScene,
               let delegate = scene.delegate as? SceneDelegate {
                return delegate
            }
        }
        return nil
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        sceneDelegate?.borat() // "great success"
    }
}

Я сохраняю ссылку на 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

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

- (void)viewDidLoad {
    [super viewDidLoad];

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

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

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

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

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

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

версия Objective-C:

          NSSet<UIScene *> * sceneArr = [[UIApplication sharedApplication] connectedScenes];
    UIScene * scene = [[sceneArr allObjects] firstObject];
    NSObject * sceneDelegate = (NSObject *)scene.delegate;

    // for example, try to get variable "window"
    UIWindow *currentKeyWindow = [sceneDelegate valueForKey: @"window"];
    NSLog(@"%@", currentKeyWindow);
Другие вопросы по тегам