UIApplication.shared.delegate 相当于 SceneDelegate xcode11?

UIApplication.shared.delegate equivalent for SceneDelegate xcode11?

我在我的 SceneDelegate 中定义了一个 let 属性。我希望某些 ViewController 能够在场景中访问它。

在 UIKit 中,我可以像这样访问 App Delegate 属性:

UIApplication.shared.delegate

然后转换并指定 属性 名称...

是否有等效项可以从 UIViewController 的实例中获取对视图控制器所在的 SceneDelegate 的引用?

截至 iOS 13,UIApplicationconnectedScenes 属性 即 Set<UIScene>。这些场景中的每一个都有一个 delegate,这是一个 UISceneDelegate。所以您可以通过这种方式访问​​所有代表。

一个场景可以管理一个或多个windows(UIWindow),你可以从它的windowScene中得到一个window的UIScene属性.

如果您想要特定视图控制器的场景委托,请注意以下几点。从 UIViewController 你可以从它的视图中得到它的 window 。从window你可以得到它的场景,当然从场景你可以得到它的委托。

简而言之,从视图控制器,您可以:

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

然而,很多时候视图控制器没有 window。当一个视图控制器呈现另一个全屏视图控制器时,就会发生这种情况。当视图控制器位于导航控制器中并且视图控制器不是顶部可见视图控制器时,可能会发生这种情况。

这需要一种不同的方法来查找视图控制器的场景。最终,您需要结合使用遍历响应器链和视图控制器层次结构,直到找到通向场景的路径。

以下扩展将(可能)从视图或视图控制器中获取 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 至少被调用一次后,您才能从视图控制器获取场景。如果您早点尝试,那么视图控制器可能还不是视图控制器层次结构的一部分。

即使视图控制器的视图的 window 为 nil,只要视图控制器是视图控制器层次结构的一部分并且在该层次结构上方的某处,它会附加到 window.


这是 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

我能够使用这个来让它工作:

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

乔·加林德 谢谢 我也用类似的方法解决了。

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

我在 MasterViewController:

中使用它
- (void)viewDidLoad {
    [super viewDidLoad];

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

self.view.window 此时为零,因此我必须联系其视图已加载并添加到 window.

的父对象

如果您使用的是拆分视图控制器并且场景委托是拆分委托,那么您可以完全避免 window/scene,只需执行以下操作:

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

我在 DetailViewController 中使用它。

如果您只是想找到 window:

,则不需要参考 SceneDelegate
(UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first

如果您的应用只有一个场景和一个 window,您应该只使用这种方法。

我将 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

访问

有时 window 会变成 nil 你可以在 plist 下查看故事板名称,请参考图片 -

iOS 13+, Swift

场景代表应用程序 UI 的实例,每个场景都保持自己的、完全独立的状态。应用程序本身永远不会超过单个实例,它维护对所有这些连接场景的引用。场景是 类 所以它们是参考类型。因此,只需在连接时维护对场景本身的引用,然后在 UIApplication.shared.

内的连接场景集中找到该场景
// Create a globally-accessible variable of some kind (global
// variable, static property, property of a singleton, etc.) that
// references this current scene (itself).
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
        }
        // Save the reference when the scene is born.
        currentScene = scene
    }
    
    func borat() {
        print("great success")
    }
}

// Here is a convenient view controller extension.
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"
    }
}

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