iOS16で画面回転できないときの対応

## はじめに
久しぶりにQiitaで投稿。
やはり書きやすい✨

普段は動画再生機能を持ったアプリを開発しており、YouTube(下記スクショ)などにも見られる全画面切り替えボタンで縦画面と横画面が切り替わる仕様です。
<img width=700 alt=img(全画面切り替えボタン) src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667697/a10b3ee1-09bc-d00e-b172-1586ab36d337.png">

本記事では、iOS16で画面回転できない問題に遭遇した(iOS15までは正常にできていた)ので対応をメモ程度に残しておきます。

## 縦画面と横画面の切り替え

今までどうしていたかと言うと、縦画面と横画面の切り替えボタンがタップされたときに、以下のように強制的に画面を回転していました。

```Swift: HogeViewController
@objc private func tappedRotateButton(_ sender: UIButton) {
    UIDevice.current.setValue(orientation.rawValue, forKey: "orientation")
}
```

iOS16では、`UIDevice.orientation`がサポートされなされなくなったことにより、上記のコードが無効化されていました。
こちらについては、WWDC2022の[What's new in UIKit](https://developer.apple.com/videos/play/wwdc2022/10068/)でも言及されており(23:00あたり)、既に[Developer Forums](https://developer.apple.com/forums/thread/707735)にthreadが立てられていました🙏

結論としては、iOS16からは`UIWindowScene`の[requestGeometryUpdate(_:errorHandler:)](https://developer.apple.com/documentation/uikit/uiwindowscene/3975944-requestgeometryupdate)を使用して強制的に画面回転を要求できます。
引数に[UIWindowScene.GeometryPreferences.iOS](https://developer.apple.com/documentation/uikit/uiwindowscene/geometrypreferences/ios)というクラスオブジェクトを渡すことができ、このクラスのイニシャライザにビューコントローラがサポートする向きを指定できます。
iOS16以降でしか使用できないため、以下のように処理を分けて回転方向を指定する必要がありますが、画面回転しない問題は無事解決できました。

```Swift: HogeViewController
    /// 強制的に画面回転を要求する
    private func rotate(orientation: UIInterfaceOrientation) {
        if #available(iOS 16.0, *) {
            guard let windowScene = view.window?.windowScene else { return }
            windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: orientation == .portrait ? .portrait : .landscapeRight)) { error in
                // 画面回転の要求が拒否された場合の処理
            }
        } else {
            UIDevice.current.setValue(orientation.rawValue, forKey: "orientation")
        }
    }
```

(追記) 
[requestGeometryUpdate(_:errorHandler:)](https://developer.apple.com/documentation/uikit/uiwindowscene/3975944-requestgeometryupdate)の引数に渡す[UIWindowScene.GeometryPreferences.iOS](https://developer.apple.com/documentation/uikit/uiwindowscene/geometrypreferences/ios)というクラスオブジェクトには、引数に[UIInterfaceOrientationMask](https://developer.apple.com/documentation/uikit/uiinterfaceorientationmask)型を渡す必要がありますが、iOS15以前の画面回転で使用しているsetValueメソッドの引数には、[UIInterfaceOrientation](https://developer.apple.com/documentation/uikit/uiinterfaceorientation)型を渡す必要があるため注意が必要です。
(追記: @seiya-suzuki さんよりコメントにてご指摘いただきました。)

## おわりに

iOS16対応が徐々に出てきていて怖いです💧笑
ちゃんとキャッチアップしないと。。

#### 余談

デバイスの向きによっても画面を回転させてUIを更新していたのですが、iOS16でレイアウトが見事に崩れました😭
以下のようにNotificationCenterから画面回転時の通知を受け取り、そこでUIの更新処理などを書いていました。

```Swift: HogeViewController
    override func viewDidLoad(_ animated: Bool) {
        super.viewDidLoad(animated)
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(didChangeOrientation),
                                               name: UIDevice.orientationDidChangeNotification,
                                               object: nil)
    }

    // ...

    @objc private func didChangeOrientation() {
        // 画面回転後のUI更新処理など
    }
```

まだ詳しく調べられていないのですが、画面回転後の画面サイズが決定するタイミングなどが変わったのでしょうか？UIの更新などの処理が上手く機能しなくなりました。
一旦[viewWillTransition(to:with:)](https://developer.apple.com/documentation/uikit/uicontentcontainer/1621466-viewwilltransition)で画面回転後のサイズが確定してからUI更新処理を実行するように対応したのですが、iOS16で具体的に何が変更されたのか知っている方がいればぜひ教えていただけると幸いです🙇‍♂️

```Swift: HogeViewController
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate { _ in
            // 画面回転後のUI更新処理など
        }
    }
```

また調べて何かわかったら追記します。

## 参考文献

この記事は、以下の情報を参考にしました。

- [What's new in UIKit](https://developer.apple.com/videos/play/wwdc2022/10068/)
- [Developer Forums - iOS16 change orientaion](https://developer.apple.com/forums/thread/707735)