SceneDelegate内にletプロパティを定義しました。いくつかのViewControllersがシーン内でそれにアクセスできるようにしたいと思います。
UIKitでは、次のようにApp Delegateプロパティにアクセスできます。
UIApplication.shared.delegate
次に、プロパティ名をキャストして指定します...
UIViewControllerのインスタンスからビューコントローラーが含まれているSceneDelegateへの参照を取得する同等のものはありますか?
IOS 13以降、UIApplication
にはconnectedScenes
プロパティがあり、これはSet<UIScene>
。これらの各シーンにはdelegate
があり、これはUISceneDelegate
です。したがって、その方法ですべてのデリゲートにアクセスできます。
シーンは1つ以上のウィンドウ(UIWindow
)を管理でき、そのUIScene
プロパティからウィンドウのwindowScene
を取得できます。
特定のビューコントローラーのシーンデリゲートが必要な場合は、次の点に注意してください。 UIViewController
から、ビューからウィンドウを取得できます。窓からシーンを取得でき、シーンから代理オブジェクトを取得できます。
要するに、ビューコントローラーから、あなたは次のことができます:
let mySceneDelegate = self.view.window.windowScene.delegate
ただし、ビューコントローラーにウィンドウがない場合はたくさんあります。これは、ビューコントローラーが別のフルスクリーンビューコントローラーを提示するときに発生します。これは、ビューコントローラーがナビゲーションコントローラー内にあり、ビューコントローラーが一番上に表示されるビューコントローラーではない場合に発生する可能性があります。
これには、ビューコントローラーのシーンを見つけるための別のアプローチが必要です。最終的には、シーンにつながるパスが見つかるまで、レスポンダーチェーンとView Controller階層を歩くことを組み合わせて使用する必要があります。
次の拡張機能は、ビューまたはビューコントローラーから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
が少なくとも1回呼び出された後でないと、ビューコントローラからシーンを取得できないことに注意してください。すぐに試してみると、View ControllerがまだView Controller階層の一部になっていない可能性があります。
これは、ビューコントローラーのビューのウィンドウがnilの場合でも機能します。ただし、ビューコントローラーがビューコントローラー階層の一部であり、その階層のどこかにある場合は、ウィンドウに接続されています。
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
これを使用して動作させることができました:
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 */
}
これを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
で使用しています。