我在SceneDelegate中定义了一个let属性。我希望某些ViewController能够在场景中访问它。
在UIKit中,我可以这样访问App Delegate属性:
UIApplication.shared.delegate
然后强制转换并指定属性名称…
是否有等效的方法可以从UIViewController的实例获取对视图控制器所在的SceneDelegate的引用?
从iOS 13开始,UIApplication具有的connectedScenes属性Set<UIScene>。这些场景中的每一个都有一个delegate,即UISceneDelegate。因此,您可以通过这种方式访问所有代表。
UIApplication
connectedScenes
Set<UIScene>
delegate
UISceneDelegate
一个场景可以管理一个或多个窗口(UIWindow),您可以UIScene从其windowScene属性中获取一个窗口。
UIWindow
UIScene
windowScene
如果要特定视图控制器的场景委托,请注意以下几点。从a,UIViewController您可以从其视图中获取其窗口。从窗口中可以获取其场景,当然也可以从场景中获取其委托。
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在至少调用一次之后,才能从视图控制器获取场景。如果您尽早尝试,那么视图控制器可能还不属于视图控制器层次结构。
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