iOSアプリのMultitasking対応について

## はじめに
WWDC2019にて2020年4月までにMultitaskingをサポートすることが必須と発表されました。
そして今年1月、必須ではないにしてもMultitaskingをサポートすることを **強くお勧めする** とアナウンスがありました。
もちろん対応しなければアプリをリジェクトされるわけではありませんが、今後Multitaskingが重要となっていくことは明らかです。
そこで今回はMultitaskingの対応方法および、注意すべき点についてまとめていきます。
[iPhoneとiPadに適したユーザーインターフェイスの構築](https://developer.apple.com/jp/news/?id=01132020b)

## Multitasking設定方法
1. `Requires full screen`にチェックが入っている場合は外します
<img width="1043" alt="スクリーンショット 2020-03-28 15.17.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556106/9ed5a273-21c6-cd7d-2a58-4e4a88cb3da1.png">
2. `Device Orientation`を4つ全ての方向に対応するようにチェックします
<img width="1043" alt="スクリーンショット 2020-03-28 15.17.59 2 2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556106/7f8741f3-3f4f-9fcf-20bc-b5f30e824a06.png">
3. もし、iPadのみで全ての方向に対応するようにする場合は`General`の `Device Orientation`は弄らず、`info.plist`から`Supported interface orientations (iPad)`にて4つの方向全て対応するよう設定して下さい
<img width="1037" alt="スクリーンショット 2020-03-28 15.18.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556106/afe46216-d483-ed3f-9aa1-fbd47ac8afcf.png">
4. 最後に、もし起動画面をStoryboardではなくimageで作成している場合は`Launch Screen File`にてLaunch用の`Storyboard`を作成して設定します
<img width="1043" alt="スクリーンショット 2020-03-28 15.17.59 2 3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/556106/d5dc189a-0da2-1c61-9d8e-a21da26f67b1.png">
5. あとはiPadでビルドすればMultitaskingに対応していることを確認することができます

## Multitasking対応方法
Multitaskingの対応すると今までにはなかったSplit表示になる(画面分割される)タイミングなどでレイアウトを更新する必要が出てきます。
AutoLayoutにレイアウトの計算を全て任せることができていれば大きく対応する必要はありませんが、手動で計算をしている箇所等についてはこちらで意図的に再計算を走らせる必要があります。
もちろん、レイアウトを手動計算する方法で対応するよりもAutolayoutにて対応するのが理想ではあるとは思いますが、そう簡単に対応できるわけではありませんので、手動でのレイアウトが必要な場合の対応方法について記載します。

### レイアウト再計算処理タイミングの見直し
ViewControllerのLifecycle関数にてMultitaskingによる画面サイズの変更を検知してくれるのは大きく以下の2つです。

#### viewWillTransition(to:with:)
Multitasking(および画面回転)による画面サイズの変更は`viewWillTransition(to:with:)`にて検知できます。
また、サイズ変更後のアプリのWindowサイズは引数の`size`の中に格納されています。

```swift
/// 画面回転時、およびMultitaskingによる画面サイズ変更時に呼ばれる
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { _ in
        // 遷移アニメーション時に行う処理を記述
    }, completion: { _ in
        // 遷移終了後に行う処理を記述
    })
}
```

#### viewDidLayoutSubviews()
`viewDidLayoutSubviews`でも画面サイズの変更を検知することができます。
ただし、こちらはMultitaskingによる画面サイズ変更のみでなく、レイアウトが更新されるタイミング全てで呼ばれますので、コストがかなりかかってしまう可能性があります。
使用する場合は慎重に決めて下さい。

### UIScreen.main.boundsについて
`UIScreen.main.bounds`、幅や高さ計算で何かと便利で使ってしまいがちですが、これを使用してframe計算をしている箇所についてはほぼ全て修正する必要があります。
`UIScreen.main.bounds`は、あくまで **端末のサイズ** を取得するモノで、 **アプリの表示サイズ** を取得するモノではありません。
SplitViewで表示される際はアプリの表示サイズが端末サイズよりも小さい状態で表示される場合がありますので、`UIScreen.main.bounds`を使用しているとレイアウトが崩れる場合があります。
その場合は下記の方法にて、現在表示されているアプリサイズ(Window)のboundsをとってくることができますので、必要に応じて`UIScreen.main.bounds`の箇所を書き換えることで対応できます。

```swift
/// アプリの現在表示されているWindow(KeyWindow)のサイズを取得してくる
UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.bounds

/// 下記でも同様の結果を取得できるが、keyWindowがiOS13にてdisabledとなっているので使用しない
UIApplication.shared.keyWindow?.bounds
```

## おわりに
以上で基本的にはMultitaskingに対応することができると思います。
対応自体は比較的単純なのですが、規模が大きいアプリはカバーする画面数が多く大変になりがちです。
しかし、今後Multitaskingの重要性も増していくことと思いますので、是非この機会に対応してみてはいかがでしょうか？

## 参考文献
- [Slide Over and Split View Quick Start](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/AdoptingMultitaskingOniPad/QuickStartForSlideOverAndSplitView.html)
- [viewWillTransition(to:with:) - UIContentContainer | Apple Developer Documentation](https://developer.apple.com/documentation/uikit/uicontentcontainer/1621466-viewwilltransition)