Я определил свойство let в моем SceneDelegate. Я хотел бы, чтобы некоторые из ViewControllers могли получить доступ к этому внутри сцены.
В UIKit я мог получить доступ к свойствам App Delegate следующим образом:
UIApplication.shared.delegate
затем кастинг и указание имени свойства...
Есть ли эквивалент для получения ссылки на SceneDelegate, в котором находится контроллер представления, из экземпляра UIViewController?





Начиная с 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
@Fabiosoft Я обновил этот ответ, который позволяет вам получить сцену контроллера представления при большем количестве условий, даже если окно равно нулю.
Какая версия этого со SwiftUI? @rmaddy
Я смог заставить его работать, используя это:
let scene = UIApplication.shared.connectedScenes.first
if let sd : SceneDelegate = (scene?.delegate as? SceneDelegate) {
sd.blah()
}
У меня есть логика для обработки обратных вызовов OAuth в делегате сцены и замыкание, к которому я хотел получить доступ в ViewController, когда происходит обратный вызов. Этот код прекрасно позволяет мне получить доступ к делегату сцены в ViewController для доступа к этому закрытию. +1
Это допустимо только в том случае, если в вашем приложении есть одна сцена. Все преимущество вспомогательных сцен заключается в том, что у вас может быть более одной сцены. Такой код не будет работать должным образом, если имеется более одной сцены.
да. Конечно. Это «ярлык» для односценного проекта. Вам нужно будет пройти через массивconnectedScenes, если вы хотите получить доступ более чем к одной сцене.
Это очень хорошо сработало для интеграции Azure MSAL SDK. Спасибо за это, мы сэкономили время на кодировании, чтобы сделать модальное окно проверки подлинности MSAL видимым. Полностью просмотрел это свойство в документации Apple UIApplication.
Это неверно, вы получаете любой первый подключенный UIScene, а не UIScene родитель UIViewController.
@PedroPauloAmorim: это ярлык для 90% проектов, в которых есть только один UIScene. Это три строки кода, чтобы быстро добиться этого.
Тем не менее, это не правильное решение, потому что вы получаете значение от глобального контроллера, а не от вашего текущего UIScene.
Джо Галинд Спасибо Я тоже решал подобным образом.
// 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 на данный момент равен нулю, поэтому мне пришлось обратиться к родителю, представление которого уже загружено и добавлено в окно.
Если вы используете контроллер с разделенным представлением, а делегат сцены является разделенным делегатом, вы можете вообще избежать окна/сцены и просто сделать:
SceneDelegate *sceneDelegate = (SceneDelegate *)self.splitViewController.delegate;
Я использую это в своем DetailViewController.
Вам не нужно ссылаться на SceneDelegate, если вы только пытаетесь найти окно:
(UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first
Вы должны использовать этот подход только в том случае, если ваше приложение будет иметь только одну сцену и одно окно.
Я сохраняю ссылку на 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
Xcode 13+, 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"
}
}
Это должен быть выбранный ответ! Кроме того, это забавно, так что это происходит.
У меня нулевое значение для self.view.window... как я могу получить доступ к текущему окну текущей сцены? Как и «старый» appDelegate.window