Swift でデバイスの向きを正しく取得するには
Swift ではデバイスの傾きを取得するプロパティとして、
UIInterfaceOrientationUIDeviceOrientationAVCaptureVideoOrientation
の三つが存在します。以下、それぞれの対応表と Enum での値を載せておきます。これ、いつもめっちゃ忘れるのでちゃんと覚えておきたいですね。
| UIInterfaceOrientation | UIDeviceOrientation | AVCaptureVideoOrientation | |
|---|---|---|---|
| unknown | 0 | 0 | - |
| portrait | 1 | 1 | 1 |
| portraitUpsideDown | 2 | 2 | 2 |
| landscapeLeft | 3 | 3 | 4 |
| landscapeRight | 4 | 4 | 3 |
| faceUp | - | 5 | - |
| faceDown | - | 6 | - |
UIInterfaceOrientation
UIInterfaceOrientationは簡単に言うとステータスバーの向きです。なのでデバイスが上を向いているか下を向いているかの判定faceUpやfaceDownはありません。ステータスバーが表示されれば値はとってこれるのですが、それまではunknownが入っています。
なのでviewDidLoadなどで呼び出すと多分unknownが入っていて意味のあるデータが取れません。ステータスバーを非表示にしているときはどうなるんでしょう。
調べたところどうも
viewDidAppear以降に呼ぶと良いらしい。
取得方法
iOS13 以降はUIWindowSceneを使えとのことなのでちょっとめんどくさいですがUIApplicationのextensionを使って実装します。
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はカメラの向きです。何故かこれだけlandscapeLeftとlandscapeRightのrawValueの値が逆になっています。こういう頭のおかしい設計、誰が許可したんですか。