Swiftにおけるorientation 操作

view controllerごとにorientationを操作した経験より(2020-08-15)

畑田です。 OTOnectの開発においてview controllerごとにorientationを細かく設定する機会があったので記録に残します。
全てコードで書いています。

環境

Swift version 5.2.4
Xcode version 11.6

fix application orientation

アプリ全体で画面の向きを固定する場合は、Info.plistSupported interface orientationsにおいて設定します。
例えばSupported interface orientationsPortrait (bottom home button)のみにすると、縦画面のみがサポートされますし、Portrait (bottom home button)Portrait (top home button)Landscape (left home button)Landscape (right home button)にすると全ての画面の向きがサポートされます。

fix launch screen orientation

launch screenの画面の向きはapplicationがlaunchされる前の設定なので、コードで設定するというよりはInfo.plistSupported interface orientationsにおいて設定します。
ただし、こうしてしまうとアプリ全体がそのorientationに固定されてしまうので、設定しなおしてあげる必要があります。
これはAppDelegate.swiftに記述してあげます。以下を参照してください。

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.allButUpsideDown
}

こうしてあげると、launch後の画面はXcodeのデフォルトの設定(上下反対の縦画面以外をサポートする設定)に戻ります。

fix orientation of each view controller

view controllerごとの画面の向きの設定はUIViewControllershouldAutorotatesupportedInterfaceOrientationをoverrideしてあげることで実現します。

override var shouldAutorotate: Bool {
    return false
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.portrait
}

fix orientation of navigation controller or tab bar controller

navigation controllerなどを使っている場合は、そのorientationをvisibleViewControllerのorientationに従わせるようなextensionを追加することで実現できます。

import UIKit

extension UINavigationController {
    open override var shouldAutorotate: Bool {
        guard let viewController = self.visibleViewController else { return true }
        return viewController.shouldAutorotate
    }

    open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        guard let viewController = self.visibleViewController else { return .all }
        return viewController.supportedInterfaceOrientations
    }
}

set orientation when the view appears

それぞれのview controllerが立ち上がったときに画面の向きを指定したい場合は、以下のようなメソッドで実現可能です。
これを画面の向きを強制しタイミングで呼び出してあげるだけです。
もちろん.portraitの部分を変えてあげれば他のorientationにも対応できます。

UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")

ただし、このメソッドは強制的に画面の向きを指定したように回転させるので、呼び出したときにshouldAutorote propertyがtrueになっていないとエラーが起こります。
しかも、このメソッドは呼び出すたびに一度だけ強制的に画面を回転させるだけで、その後の画面の向きの保持には関わりません。
したがって、shouldAutorotateの値をflagで管理し、以下のようにするのが自分なりのpracticeです。

class EachViewController: UIViewController {
    
    // flag
    private var canRotate = true

    override var shouldAutorotate: Bool {
        return canRotate
    }

    override func viewDidLoad() { // viewDidLayoutSubviews()などでも
        super.viewDidLoad()

        // free orientation
        canRotate = true

        // force orientation change
        UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")

        // fix orientation
        canRotate = false

        // skip details
    }

}