[SwiftUI]UIWindowを取得する方法
iOSでSwiftUIを使って開発していても、UIWindowにアクセスする必要が出て来るケースがあります。例えば、AuthenticationServicesフレームワークなどです。
UIWindowにアクセスする方法はいくつかあるのですが、よりシンプルでスマートな方法を調べてみました。
- SwiftUI Lifecycleを使う
- Multi windowに対応する
環境
Xcode: 13.4
iOS: 15以降
実装方法
-
UIApplicationDelegateAdapter
を設定して自作のAppDelegate
を利用する -
AppDelegate
で自作のSceneDelegate
を利用するように設定する -
SceneDelegate
内でUIWindowScene
からkeyWindow
(UIWindow) を取り出す -
SceneDelegate
をObservableObject
に適合させる - 使用するViewで
EnvironmentObject
としてSceneDelegate
を定義する
UIApplicationDelegateAdapterについては、以下の公式ドキュメントを参考にしています。
App
からSceneDelegate
の実装までは下記になります。
@main
struct SwiftUIWindowSampleApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
configuration.delegateClass = SceneDelegate.self
return configuration
}
}
final class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
window = (scene as? UIWindowScene)?.keyWindow
}
}
これで準備は整いました。実際にView
内で使用してみます。
struct ContentView: View {
@EnvironmentObject var sceneDelegate: SceneDelegate
var body: some View {
Text("Hello, world!")
.padding()
.onAppear {
debugPrint(sceneDelegate.window ?? "null")
}
}
}
@EnvironmentObject
として、先に定義したSceneDelegate
を取り出します。
特にContentView
に対して.environmentObject(_)
でObservableObjectを渡さなくても、上記のようにSceneDelegate
が使えます。
理由は、公式ドキュメントに書いてあります。
If your app delegate conforms to the ObservableObject protocol, as in the example above, then SwiftUI puts the delegate it creates into the Environment. You can access the delegate from any scene or view in your app using the EnvironmentObject property wrapper:
つまり、もし上記のAppDelegate
をObservableObject
に適合すれば、UIApplicationDelegateAdaptor
さえ宣言すると、追加実装なしでAppDelegate
がEnvironmentObject
として取り出せます。
As with the app delegate, if you make your scene delegate an observable object, SwiftUI automatically puts it in the Environment, from where you can access it with the EnvironmentObject property wrapper, and create bindings to its published properties.
つまり、もし上記のSceneDelegate
をObservableObject
に適合すれば、UIApplicationDelegateAdaptor
さえ宣言すると、追加実装なしでSceneDelegate
がEnvironmentObject
として取り出せます。
SceneDelegate内のUIWindow
は、それぞれのWindowごとに保持されるので、Multi windowsにも対応しており、View
が所属するUIWindow
が取得できます。
参考
Discussion