我在我的 SceneDelegate 中定义了一个 let 属性。我希望某些 ViewController 能够在场景中访问它。
在 UIKit 中,我可以像这样访问 App Delegate 属性:
UIApplication.shared.delegate
然后转换并指定属性名称...
是否有等效的方法可以从 UIViewController 的实例中获取对视图控制器所在的 SceneDelegate 的引用?
我在我的 SceneDelegate 中定义了一个 let 属性。我希望某些 ViewController 能够在场景中访问它。
在 UIKit 中,我可以像这样访问 App Delegate 属性:
UIApplication.shared.delegate
然后转换并指定属性名称...
是否有等效的方法可以从 UIViewController 的实例中获取对视图控制器所在的 SceneDelegate 的引用?
从 iOS 13UIApplication
开始,connectedScenes
属性为Set<UIScene>
. 这些场景中的每一个都有一个delegate
which 是 a UISceneDelegate
。因此,您可以通过这种方式访问所有代表。
一个场景可以管理一个或多个窗口(UIWindow
),您可以UIScene
从其windowScene
属性中获取一个窗口。
如果您想要特定视图控制器的场景委托,请注意以下事项。从 aUIViewController
你可以从它的视图中得到它的窗口。从窗口你可以得到它的场景,当然你也可以从场景中得到它的委托。
简而言之,从视图控制器中,您可以执行以下操作:
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
I was able to get it to work using this:
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
只有当您的应用程序只有一个场景和一个窗口时,您才应该使用这种方法。
Xcode 13+,iOS 13+,斯威夫特
场景代表应用程序 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"
}
}
我将 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
.