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