swift UI应用程序.shared.委托是否等同于场景委托xcode11?

rseugnpd  于 2023-03-07  发布在  Swift
关注(0)|答案(9)|浏览(147)

我在SceneDelegate中定义了一个let属性。我希望一些ViewController能够在场景中访问它。
在UIKit中,我可以访问App Delegate属性,如下所示:

UIApplication.shared.delegate

然后转换并指定属性名称...
是否存在从UIViewController的示例获取视图控制器所在的SceneDelegate的引用的等效方法?

oug3syen

oug3syen1#

从iOS 13开始,UIApplication拥有connectedScenes属性Set<UIScene>。每个场景都拥有delegate属性UISceneDelegate。因此,您可以通过这种方式访问所有代理。
一个场景可以管理一个或多个窗口(UIWindow),您可以从窗口的windowScene属性获取窗口的UIScene
如果你想要一个特定视图控制器的场景代理,请注意以下几点:从UIViewController你可以从它的视图得到它的窗口,从窗口你可以得到它的场景,当然从场景你也可以得到它的代理。
简而言之,从视图控制器中,您可以执行以下操作:

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

但是,视图控制器没有窗口的情况也有很多。当视图控制器显示另一个全屏视图控制器时会发生这种情况。当视图控制器位于导航控制器中,并且视图控制器不是顶部可见的视图控制器时,会发生这种情况。
这需要一种不同的方法来找到视图控制器的场景,最终你需要使用遍历响应器链和视图控制器层次结构的组合,直到你找到一条通往场景的路径。
下面的扩展将(可能)从视图或视图控制器中获取UIScene。一旦你有了场景,你就可以访问它的代理。
添加UIResponder+场景.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实现:
UI响应器+场景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

UI响应器+场景。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
cld4siwp

cld4siwp2#

如果您的应用程序将永远只有一个场景,那么我能够让它工作使用这个:

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

vjhs03f73#

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

ffscu2ro4#

如果只是试图查找窗口,则不需要引用SceneDelegate

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

只有当您的应用只有一个场景和一个窗口时,才应使用此方法。

busg9geu

busg9geu5#

iOS 13及更高版本、Swift

场景代表应用程序用户界面的示例,每个场景都保持着自己的完全独立的状态。应用程序本身永远不会超过一个示例,它维护着对所有这些相连场景的引用。场景是类,因此它们是引用类型。因此,当场景被连接时,简单地保持对场景本身的引用,然后在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"
    }
}
svdrlsy4

svdrlsy46#

我将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访问场景

a2mppw5e

a2mppw5e7#

我在我的MasterViewController中使用这个:

- (void)viewDidLoad {
    [super viewDidLoad];

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

此时self.view.window为nil,因此我必须访问其视图已经加载并添加到窗口中的父视图。
如果你使用的是分割视图控制器,并且场景代理是分割代理,那么你可以完全避开窗口/场景,只需要:
SceneDelegate *sceneDelegate = (SceneDelegate *)self.splitViewController.delegate;
我在我的DetailViewController中使用这个。

wn9m85ua

wn9m85ua8#

有时窗口是空的,你可以检查plist下的故事板名称,请参考图片-x1c 0d1x

cnh2zyt3

cnh2zyt39#

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

相关问题