SwiftUIアプリでUIの向きを取得する
画面の向きを取得しーよっぺ!と思って、取得がラクチンな
let orientation: UIDeviceOrientation = UIDevice.current.orientation
を参照するのは(状況によっては)間違い。
UIDeviceOrientation は現実の空間に対するデバイスの物理的な傾きを 6 方向で表す。これは、デバイスを垂直に立てたときの回転方向に加えて、仰向け・うつ伏せの状態も含まれてくる。もうちょっと言い換えると、xyz 軸それぞれ正負の 6 方向。これらの 6 状態(と unknown )を遷移する。
それが欲しいケースならこれで良いが、ここでは「デバイスの物理的な上下左右 4 辺に対して、画面上の UI がどの向きに表示されているか」を考える。
👇 の記事のようにカメラ映像の向きを修正するために使ったりする。
この状態は UIWindowScene のプロパティにある interfaceOrientation: UIInterfaceOrientation で表される。上下左右の 4 状態(と unknown)を遷移する。
UIWindowScene のインスタンスは AppDelegate からの SceneDelegate から取得するしかない。が、純粋な SwiftUI アプリケーションに AppDelegate は存在しない。
ここで UIApplicationDelegateAdaptor を使うことで、 AppDelegate にしか存在しない機能を SwiftUI アプリケーションの中でも利用可能になる。
👇 これで、後述の AppDelegate クラスが勝手にインスタンス化される。
@main
struct App: SwiftUI.App {
@UIApplicationDelegateAdaptor var appDelegate: AppDelegate
var body: some Scene {
/* ... */
}
}
AppDelegate は通常通り NSObject と UIApplicationDelegate を実装する。
加えて ObservableObject を実装すると、 @EnvironmentObject を使って取り出すことができるようになる。(そのために .environmentObject(appDelegate) を呼び出す必要はない。)
AppDelegate で 👇 のように後述の SceneDelegate クラスを指定することで勝手にインスタンス化され、シーンに対するデリゲートとして機能する。
class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let config = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
config.delegateClass = SceneDelegate.self
return config
}
}
SceneDelegate は通常通り NSObject と UIApplicationDelegate を実装する。
加えて ObservableObject を実装すると、こちらも @EnvironmentObject を使って取り出すことができるようになる。(そのために .environmentObject(sceneDelegate) を呼び出す必要はない。)
sceneWillEnterForeground と、念の為 sceneDidBecomeActive で、アプリ起動時の値を確認する。
windowScene(_:didUpdate:interfaceOrientation:traitCollection:)は回転やリサイズ時のイベント。ここで向きの変化を知る。
class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {
@Published var interfaceOrientation: UIInterfaceOrientation = .unknown
func sceneWillEnterForeground(_ scene: UIScene) {
if let scene = scene as? UIWindowScene {
interfaceOrientation = scene.interfaceOrientation
}
}
func sceneDidBecomeActive(_ scene: UIScene) {
if let scene = scene as? UIWindowScene {
interfaceOrientation = scene.interfaceOrientation
}
}
func windowScene(_ windowScene: UIWindowScene, didUpdate previousCoordinateSpace: UICoordinateSpace, interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation, traitCollection previousTraitCollection: UITraitCollection) {
interfaceOrientation = windowScene.interfaceOrientation
}
}
任意の View で、👇 こんなふうに参照する。
class MyView: View {
@EnvironmentObject sceneDelegate: SceneDelegate
var body: some View {
/* ... */
sceneDelegate.interfaceOrientation
/* ... */
}
}
Discussion