【Swift4】特定の画面で回転を制御する方法について、サンプルを作って理解する

仕事で実装する際にハマったので、復習のためにサンプルプロジェクトを作ってみました。

今回は特に分かりづらかったTabBarController/NavigationControllerを使う場合にフォーカスを当てて、「基本は回転を許すが、特定の画面では回転させたくない」というケースを考えてみます。

# 下準備
「基本は回転を許す」ので、プロジェクトのDevice Orientationは以下の通り設定します。
<img width="685" alt="スクリーンショット 2018-10-31 22.56.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/112818/c8477bfa-5a35-34d1-4a78-8884e1ac1fbf.png">



# ①TabBarControllerもNavigationControllerも使わない場合
まずはシンプルな2画面があり、ほげほげVCだけは回転させたくない場合について。
<img width="709" alt="スクリーンショット 2018-10-31 23.07.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/112818/cae22567-bd66-34e1-c088-995ff4d5d30a.png">

## 解決法
とても簡単で、回転させたくないViewController内に以下を追加するだけでOKです。

```swift:HogehogeViewController.swift
// 画面を回転させるかどうか
override var shouldAutorotate: Bool {
    return true
}
    
// 回転方向の指定
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
}
```

ちなみに、今回は拡張性を考慮して↑のように書きましたが、下記のようにシンプルに`shouldAutorotate`に`false`を返すやり方でも同じ結果（=回転させない）を得ることができます。（`shouldAutorotate`が`true`でないと、`supportedInterfaceOrientations`プロパティは呼ばれないので書かなくてOK）

```swift:HogehogeViewController.swift
override var shouldAutorotate: Bool {
    return false
}
```

# ②TabBarControllerを使う場合
次にTabBarControllerをベースにした2画面があり、ほげほげVCだけは回転させたくない場合について。
<img width="497" alt="スクリーンショット 2018-10-31 23.16.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/112818/3db90066-de74-d42b-5128-2299caab742e.png">
とりあえず①と同じコードをほげほげVCに書いてビルドしてみましたが、制御が効かずにほげほげVCも回転してしまいました。

`supportedInterfaceOrientations`の[ドキュメント](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations#declarations)を見てみると、

>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.

とあります。なるほど、TabBarControllerやNavigationControllerを使っているとルートのViewControllerが呼ばれるから、回転させたくないViewControllerに書くだけではダメだったんですね。

##解決法
UITabBarController+Orientation.swiftというファイルを作り、TabBarControllerを拡張します。
**ポイントは`selectedViewController`を使って、選択されたタブのViewControllerを取得し、そこで設定された`shouldAutorotate`と`supportedInterfaceOrientations`プロパティを取ってくること。**回転させるか否かをTabBarController側では判断せずに、各ViewControllerに委譲する形になります。

```swift:UITabBarController+Orientation.swift
extension UITabBarController {

    open override var shouldAutorotate: Bool {
        // 選択されたタブのViewControllerを取得
        if let vc = selectedViewController {
            return vc.shouldAutorotate
        } else {
            return true
        }
    }
    
    open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        // 選択されたタブのViewControllerを取得
        if let vc = selectedViewController {
            return vc.supportedInterfaceOrientations
        } else {
            return .allButUpsideDown
        }
    }

}
```

その上で、回転させたくないViewControllerに①と同じコードを書きます。

```swift:HogehogeViewController.swift
override var shouldAutorotate: Bool {
    return true
}
    
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
}
```

これで無事に狙った動きをさせることができました🎉

# ③NavigationControllerを使う場合
最後にNavigationControllerを使った画面があり、ほげほげVCだけ回転させたくない場合を見てみます。
<img width="863" alt="スクリーンショット 2018-11-01 0.31.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/112818/113b6df2-3482-5120-42be-f1f3a931356f.png">

お約束的に①と同じコードをほげほげVCに直接実装してみますが、ルートのNavigationControllerが呼ばれるためにうまくいかず、ほげほげVCも回転してしまいました。

##解決法
そこで今度はNavigationController+Orientation.swiftというファイルを作り、NavigationControllerを拡張します。
**ポイントは`viewControllers.last`を使って、NavigationControllerにぶら下がっているViewControllerを取得し、そこで設定された`shouldAutorotate`と`supportedInterfaceOrientations`プロパティを取ってくること。**TabBarControllerのときと原理は同じですね。

```swift:UINavigationController+Orientation.swift

extension UINavigationController {

    open override var shouldAutorotate: Bool {
        // NavigationControllerにぶら下がっているViewControllerを取得
        if let vc = viewControllers.last {
            return vc.shouldAutorotate
        } else {
            return true
        }
    }
    
    open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        // NavigationControllerにぶら下がっているViewControllerを取得
        if let vc = viewControllers.last {
            return vc.supportedInterfaceOrientations
        } else {
            return .allButUpsideDown
        }
    }

}
```

その上で、回転を制御したいほげほげVCに①と同じコードを追加します。

```swift:HogehogeViewController.swift
override var shouldAutorotate: Bool {
    return true
}
    
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
}
```

これで無事に狙った動きを得ることができました🎉ここまで来るとだいぶ回転制御がわかってきた気がします。

TabBarController/NavigationControllerの両方を使う場合も書きたかったのですが、疲れてきたのでそれはまた次回にします:wave:

# 参考になった記事
* [一部のViewControllerだけ端末の回転をさせる方法 - Qiita](https://qiita.com/YuwUnknown/items/876f5f1c1734ce6a7651)
* [Swift 3.0 ViewController単位で画面の向きを制御する - Qiita](https://qiita.com/chano2/items/42c8f1d1ff4eb8b00896)
* [【iOS】画面の向き（Orientation）を特定のページのみで制限したり許可したりしたい【supportedInterfaceOrientations】 - DRYな備忘録](http://otiai10.hatenablog.com/entry/2016/09/19/044605)