1. ホーム
  2. swift

UIApplication.shared.delegate は、SceneDelegate xcode11 に相当しますか?

2023-07-16 13:10:25

質問

SceneDelegate内でletプロパティを定義しました。私は、いくつかのViewControllerがシーン内でそれにアクセスできるようにしたいです。

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

しかし、ビューコントローラがウィンドウを持たないこともたくさんあります。これは、ビューコントローラが別のフルスクリーンビューコントローラを表示しているときに起こります。これは、ビューコントローラがナビゲーションコントローラ内にあり、そのビューコントローラが一番上の可視ビューコントローラでない場合に起こりえます。

これは、ビューコントローラーのシーンを見つけるために、異なるアプローチを必要とします。最終的には、シーンにつながるパスを見つけるまで、レスポンダチェーンとビューコントローラの階層を歩くことの組み合わせを使用する必要があります。

以下の拡張機能は、ビューまたはビューコントローラから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 が少なくとも一度は呼び出された後でなければならないことに注意してください。もし、それ以前に呼び出した場合は、そのビューコントローラがまだビューコントローラ階層の一部でない可能性があります。

これは、ビューコントローラがビューコントローラ階層の一部であり、その階層のどこかでウィンドウにアタッチされている限り、ビューコントローラのビューのウィンドウが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