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