🐦

SwiftUIアプリでUIの向きを取得する

2022/02/18に公開

https://qiita.com/taisei_hikawa/items/2b6dbdbfebec812398b4


画面の向きを取得しーよっぺ!と思って、取得がラクチンな

let orientation: UIDeviceOrientation = UIDevice.current.orientation

を参照するのは(状況によっては)間違い。

UIDeviceOrientation現実の空間に対するデバイスの物理的な傾きを 6 方向で表す。これは、デバイスを垂直に立てたときの回転方向に加えて、仰向け・うつ伏せの状態も含まれてくる。もうちょっと言い換えると、xyz 軸それぞれ正負の 6 方向。これらの 6 状態(と unknown )を遷移する。

それが欲しいケースならこれで良いが、ここでは「デバイスの物理的な上下左右 4 辺に対して、画面上の UI がどの向きに表示されているか」を考える。
👇 の記事のようにカメラ映像の向きを修正するために使ったりする。

https://zenn.dev/ztrehagem/articles/ios_mlkit_selfie_twilio

この状態は 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 は通常通り NSObjectUIApplicationDelegate を実装する。
加えて 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 は通常通り NSObjectUIApplicationDelegate を実装する。
加えて 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
        /* ... */
    }
}
GitHubで編集を提案

Discussion