ゴール
たとえば
- 基本的にPortrait(縦向き)のみに制限したいんだけど、特定の画面だけではLandscape(横向き)を許可したい
特定のViewで、強制的に向きを変えることはできる
けど、これは向きを変えるだけであって、ふたたび端末をぐるっとすると縦向きに戻ったりする。かなしい。
override func viewDidLoad() { super.viewDidLoad() // Force Orientation Landscape UIDevice.currentDevice().setValue(UIInterfaceOrientation.LandscapeLeft.rawValue, forKey: "orientation") }
- objective c - How to force view controller orientation in iOS 8? - Stack Overflow
- しかも、あんまり推奨されるものではないっぽい雰囲気がある
Info.plistで、全体的に使うOrientationは制限できる
プロジェクトファイル(file path的にはInfo.plist)で、以下のような項目があるから「これでいーじゃーん」と思ってチェックしても、これはアプリケーション全体で許可非許可の扱いなので、「◯◯というViewだけは横ね」みたいにホワイトリスト的な挙動は設定できない。
supportedInterfaceOrientationsを使う
ViewControllerのメソッド(shouldAutoRotateと)supportedInterfaceOrientationを使えば、ViewControllerごとにサポートするOrientationを明示的に指定できる。
- ios - how to lock portrait orientation for only main view using swift - Stack Overflow
- の、コメント
- shouldAutorotate - UIViewController | Apple Developer Documentation
- supportedInterfaceOrientations - UIViewController | Apple Developer Documentation
class MyViewController: UIViewController { // ... // ここでtrueを返すと、`supportedInterfaceOrientations`が呼ばれる override func shouldAutorotate() -> Bool { // まあでもデフォルトでtrue返すっぽいし、overrideしなくていいっぽい return true } // ここでサポートできるOrientationを明示できる override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { return UIInterfaceOrientationMask.Portrait } }
supportedInterfaceOrientationsがなんかうまく動かないときに疑うこと
しかし、これだけだと(だいたいの場合)動かない(と思う)。なぜなら、
When the user changes the device orientation, the system calls this method on the root view controller or the topmost presented view controller that fills the window. supportedInterfaceOrientations #Discussion
「ユーザがデバイスの向きを変えたとき、システムはルートのViewControllerもしくは一番上で画面いっぱいに表示されているViewControllerの supportedInterfaceOrientations
を呼びます。」
とのことで、だいたいの場合、UINavigationControllerとか、TabBarControllerとか、PageViewControllerとかにこれを実装するハメになってくるんではないかと思う。
UINavigationControllerを継承したMyNavigationControllerとかを作ってもいいんですけど
一案として、上記のメソッドを実装した独自のMyNavigationController
を作って、下記のようにするのかもしれない。
class MyNavigationController: UINavigationController { override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { return UIInterfaceOrientationMask.Portrait } } // で、Storyboardなりで、起点になるNavigationControllerの // classをこいつに指定してあげれば、このメソッドが呼ばれるはず
たぶんこれでも動くんだけど、若干のだるさがあるので、標準のUINavigationControllerのextension作っちゃえばいいじゃないか、という結論になった。
最終的に
// TabBarControllerのcontextがメイン extension UITabBarController { override public func shouldAutorotate() -> Bool { return true } // もうちょっとちゃんとやる場合は、子供のViewControllerに // supportedInterfaceOrientationsを実装して // return selectedVC.supportedInterfaceOrientations() // とかすれば、判断を子供に委譲できる override public func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { guard let selectedVC = self.selectedViewController else { return UIInterfaceOrientationMask.Portrait } // TabBarControllerの中でNavigationを使ってるケースがある guard let navigation = selectedVC as? UINavigationController else { return UIInterfaceOrientationMask.Portrait } // Navigationで一番うえのやつを取得して guard let current = navigation.viewControllers.last else { return UIInterfaceOrientationMask.Portrait } // これ!このビューのときだけ横を許可して!! if current is MyCoolViewController { return UIInterfaceOrientationMask.Landscape } return UIInterfaceOrientationMask.Portrait } } // サービスの登録フローとかは、いきなりNavigationが出るので extension UINavigationController { override public func shouldAutorotate() -> Bool { return true } override public func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { return UIInterfaceOrientationMask.Portrait } } // サービスのイントロ画面は、いきなりPageViewControllerなので extension UIPageViewController { override public func shouldAutorotate() -> Bool { return true } override public func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { return UIInterfaceOrientationMask.Portrait } }
という感じになった。これを、AppDelegate.swift
のケツにでも追加しとく。今回は明示的に "ここでだけで" まとめたかったので、各々のsupportedInterfaceOrientation
を呼ぶ実装にはしなかったけど、必ず各々に判断を委譲するほうが綺麗なケースもあると思う。
雑感
- swiftのコードをインターネッツでググっても、動いたり動かなかったりする
- swiftという言語が若いからかなと思ったけど、どうやらそうでもない
- 情報の絶対量はAndroidよりも多いし
- Objective-Cの情報でも参考にできることは多いし
- おそらくアプリの要件が人それぞれで、しかもそれがけっこう切り分けられないんだと思われる
- 今回のケースでいうとroot view controllerがTabBarControllerだったり、NavigationControllerだったり、PageViewControllerだったりした
- とかそういうこと
- であるからして、慌てず焦らず、ちゃんと英語読んで、公式も当たって「なぜ動かないか」を環境非依存な知識までかみ砕いて理解していくのが大事だなあ、と思った
- 勉強になります
DRYな備忘録として