Logo
Overview

Swiftにおけるデバイスの向き判定

December 12, 2022
1 min read

Swift でデバイスの向きを正しく取得するには

Swift ではデバイスの傾きを取得するプロパティとして、

  • UIInterfaceOrientation
  • UIDeviceOrientation
  • AVCaptureVideoOrientation

の三つが存在します。以下、それぞれの対応表と Enum での値を載せておきます。これ、いつもめっちゃ忘れるのでちゃんと覚えておきたいですね。

UIInterfaceOrientationUIDeviceOrientationAVCaptureVideoOrientation
unknown00-
portrait111
portraitUpsideDown222
landscapeLeft334
landscapeRight443
faceUp-5-
faceDown-6-

UIInterfaceOrientation

UIInterfaceOrientationは簡単に言うとステータスバーの向きです。なのでデバイスが上を向いているか下を向いているかの判定faceUpfaceDownはありません。ステータスバーが表示されれば値はとってこれるのですが、それまではunknownが入っています。

なのでviewDidLoadなどで呼び出すと多分unknownが入っていて意味のあるデータが取れません。ステータスバーを非表示にしているときはどうなるんでしょう。

調べたところどうもviewDidAppear以降に呼ぶと良いらしい。

取得方法

iOS13 以降はUIWindowSceneを使えとのことなのでちょっとめんどくさいですがUIApplicationextensionを使って実装します。

extension UIApplication {
var foregroundScene: UIWindowScene? {
UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first(where: { $0.activationState == .foregroundActive})
}
}

このような計算プロパティを設定した上で、

func getCurrentDeviceOrientation() -> UIInterfaceOrientation {
guard let orientation: UIInterfaceOrientation = UIApplication.shared.foregroundScene?.interfaceOrientation else {
return .unknown
}
return orientation
}

とすればとってこれます。ないときが存在するので、そのときは適当にunknownでも設定してしまうのが良いでしょう。

傾き検出

UIInterfaceOrientationが変化するときは画面がぐるっとまわるようなアニメーションが発生するのですが、その際にviewWillTransitionが呼ばれるのでこれをオーバーライドしてしまえばステータスバーの回転を検出することができます。

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
guard let orientation: UIInterfaceOrientation = UIApplication.shared.foregroundScene?.interfaceOrientation else {
return
}
}

UIDeviceOrientation

UIDeviceOrientationが実際に言うところの正しいデバイスの向きを取得します。ただし、ジャイロで向きを判定しているのでアプリを起動してから一度もデバイスを傾けていない状態ではunknownが入ってしまいます。

なので起動直後はUIInterfaceOrientationの方が正しいデータが取れます。起動時に下向きにしている人とかはいないと思うので、多分。

取得方法

UIInterfaceOrientationと違って簡単にとってこれます。

UIDevice.current.orientation

値の設定

UIDevice.current.setValue(3, forKey: "orientation")

設定値の制限

UIViewControllerを継承しているクラスでsupportedInterfaceOrientationをオーバーライドすれば各 View ごとに対応している向きを固定できるらしいです。

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .all
}

なお、AppDelegateでも同じような事ができる模様。

class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return .all
}
}

傾き検出

UIDeviceOrientationの値が変わったかどうかはUIDeviceOrientationDidChangeNotificationを使うことで取得できます。

で、昔から使われているのがこの手法です。ただ、個人的にはselectorとか@objcとかの記法が全く好きではないのでこれ以外を使いたいなあと思っています。

class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(ViewController.orientationChanged),
name: UIDevice.orientationDidChangeNotification,
object: nil)
}
@objc func orientationChanged() {
// 処理を書く
}
}

AVCaptureVideoOrientation

AVCaptureVideoOrientationはカメラの向きです。何故かこれだけlandscapeLeftlandscapeRightrawValueの値が逆になっています。こういう頭のおかしい設計、誰が許可したんですか。