58
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Swift】SwiftUIのViewからUIWindowへアクセスする方法

Last updated at Posted at 2019-08-02

iOS13で変わったこと

iOS13以降ではiOSアプリでもマルチウィンドウで使えるようになり
アプリの起動経路が変わりました。

※ マルチウィンドウ自体はオプトインの仕組みで
ONにするにはinfo.plistのUIApplicationSceneManifestの設定が必要になります。

https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
https://developer.apple.com/documentation/bundleresources/information_property_list/uiapplicationscenemanifest

具体的には下記のSceneDelegateで画面の最初の表示を行います。


class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Use a UIHostingController as window root view controller
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: ContentView())
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

UIWindowAppDelegateからSceneDelegateに移動し
下記のにUIWindowインスタンスを取得することができなくなりました。


let delegate = UIApplication.shared.delegate as! AppDelegate
let window = delegate.window

これまで見てきた限りですと
SceneDelegateへの適切なアクセス方法が見つからず
さらにUIWindowが一つのインスタンスとは限らないため
シングルトンでインスタンスを保持していても
マルチウィンドウを使用している場合
別のUIWindowを参照してしまうリスクもあります。

どういう時にUIWindowを使うか?

例えば
Sign in With Appleなどのような認証を使用する場合
認証を行う画面を表示するためにUIWindowを提供する必要があります。


extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return self.view.window!
    }
}

SwiftUIのViewからアクセスするには?

上記のような認証をSwiftUIから使用する場合
ViewからUIWindowを提供することがあるかもしれません。

その際にViewからUIWindowのインスタンスへアクセスするにはどうしたら良いでしょうか?

@Environmentを利用する

一つの方法として
@Environmentを使ってUIWindowインスタンスをDIする方法があります。

これを実現するために
EnvironmentKeyEnvironmentValuesを活用します。

https://developer.apple.com/documentation/swiftui/environmentkey
https://developer.apple.com/documentation/swiftui/environmentvalues

まずEnvironmentKeyプロトコルに適合したstructを作成します。
必須なのはdefaultValueプロパティのみです。


struct WindowKey: EnvironmentKey {
    static let defaultValue: UIWindow? = nil
}

次に上記のEnvironmentValuesを拡張して
上記のキーのsubscriptを定義します。


extension EnvironmentValues {
    var window: UIWindow? {
        get {
            self[WindowKey.self]
        }
        set {
            self[WindowKey.self] = newValue
        }
    }
}

こうすることでenvironment(_:_:)を使ってwindowをDIすることができます。
https://developer.apple.com/documentation/swiftui/view/3289000-environment


class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            
            // ↓↓↓↓UIWindowを設定している↓↓↓↓↓↓
            let contentView = ContentView().environment(\.window, window)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

}

これでViewからは


struct ContentView: View {
    @Environment(\.window) var window
}

とアクセスすることができます。

参考
https://github.com/objcio/swift-talk-app-swiftui/blob/episode-163/Account.swift

なぜ@EnvironmentObjectではなく@Environmentのか?

実は同じようにUIWindowにアクセスできます。
しかしその場合は
UIWindowObservableObject
適合させないといけなくなります。
UIWindowを拡張するのはあまり良くないのではないかということで
@Environmentを使用しています。

まとめ

SwiftUIに触っていると
「あれ?これどうやるんだ?」と悩むことって意外と多く感じています。

まだまだ勉強中なので
これからも知見、経験をためていって使いこなせるようになりたいです😃

何か間違いなどございましたら教えていただけますと嬉しいです🙇🏻‍♂️

58
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
58
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?