UIHostingControllerのSafe Area Insetsを無効にする

この記事は、`UIHostingController`が、SwivtUIに対して`UIView.safeAreaInsets`を自動で設定するという問題を解決できたので、同じ問題で困っている方の為の記事になります。

# 問題の内容

SwiftUIを使いたいけど、いきなり全部をSwiftUIでって、なかなかハードルが高いですよね。
現在、私が担当している案件でもUIKitで全ての画面が作られていてる状況でした。

転職して半年ほど経過後、新機能開発で新規画面を作成することになったのですが、自分のタスクで`UIHostingController`を活用してSwiftUIで実装できそうな静的なビューがあったので、良い機会と思い、SwiftUIでビューを作り、`UIHostingController`で作成したビューを、Storyboardで定義されたビューにAddする、という実装を行なったのですが、ちょっとした問題に遭遇してしまいました。

それは、**`UIHostingController`が、SwivtUIに対して`UIView.safeAreaInsets`を自動で設定する** という問題です。

下のスクリーンショットは、赤色の部分とボタン部分をStoryboardで定義し、赤色の部分に表示する内容をSwiftUIで実装し、`UIHostingController`を使って配置した画面になります。

View Hierarchyはこんな感じです。
![View hierarchy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/c4c78f32-5104-fe21-81d4-e50028ae7d3a.png)

この画面、普通に表示する時には、特に問題はないのですが、Gif動画の様に、スクロールを行った状態で別画面へ遷移して戻ってくると、SwiftUIのビューに対して、Safe Area Insetsが自動的に設定され、意図しない余白（黄色の部分）ができてしまいます。

|　本来のレイアウト | スクロールを行った状態で別画面へ遷移して戻ってきた場合 |
|-----|-----|
| ![スクロールなし - Simulator - iPhone 14 Pro - iOS 16.2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/d3e91497-adc6-c787-8151-4ffd8e455e84.png) | ![スクロールあり - Simulator - iPhone 14 Pro - iOS 16.2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/37be2218-bae0-3816-e831-33189caf0606.png) |

そして、手元で確認した限りでは、iOS 15.5、iOS 16.2 のシミュレーターで発生していたので、おそらく、iOS 15 から発生する問題だと思われます。

|　Simulator - iOS 16.2 | Simulator - iOS 15.5 |
|-----|-----|
| ![Simulator - iPhone 14 Pro - iOS 16.2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/b9863c69-5f6c-d06e-a0f1-0e9254f4f1d0.gif) | ![Simulator - iPhone 13 - iOS 15.5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/6a85db26-ce7a-3088-ef11-e10a29ded9fd.gif) |

Safe Area Insetsが自動で設定されていると言うのは、上記状態のView Hierarchyを確認すると、以下のようになっていたからです。
本来のレイアウトでは、Safe Area Insetsは、`[]`と空の状態でした。

![スクロールありView hierarchy - Simulator - iPhone 14 Pro - iOS 16.2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/d024b4bb-3219-2c5a-6347-87c7174905d5.png)

# 解決方法

SwiftUI側に、`.ignoresSafeArea`、`.edgesIgnoringSafeArea`、`.safeAreaInset`を追加して制御できないか色々と試したのですが、SwiftUI側での解決方法はわかりませんでした…（こんな方法もあるよ、と言う方は、是非、コメントいただけると🙏）

案件メンバーに相談したところ、以下のリンクを教えていただきました。

https://defagos.github.io/swiftui_collection_part3/#fixing-cell-frames

記事を読んでみると、似たように、Safe Area Insetsが自動的に設定されてしまう事について書かれていました。

> the reduction in size is due to safe area insets being somehow applied

記事では、

> A Stack Overflow thread proposes a workaround for this issue until UIHostingController itself provides an official API to disable this behavior. This workaround applies swizzling to all hosting view instances indiscriminately, disabling safe area inset support entirely for all hosted SwiftUI views.
> 
> This approach is too greedy and affects hosting controllers for which this behavior is actually legitimate and desired, for example a UIHostingController hosting the SwiftUI root of a UIApplication. A more surgical approach than method swizzling is to use dynamic subclassing, the runtime wizardry applied by key-value observing.

と書かれていて、`UIHostingController`にinitを追加して、SwiftUIのSafe　Area Insetsの設定を無効にする、しないを指定できるような方法が記載されていました。

実際に試すと、スクロールを行った状態で別画面へ遷移して戻ってきても、意図しない余白（黄色の部分）は発生しませんでした。

|　Simulator - iOS 16.2 | Simulator - iOS 15.5 |
|-----|-----|
| ![Simulator - iPhone 14 Pro - iOS 16.2 - 修正後.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/ef6b187d-3fec-0660-3d00-10f31a5332f9.gif) | ![Simulator - iPhone 13 - iOS 15.5 - 修正後.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/9ca9cc5d-23ae-c7f1-6a11-3e540840dd2a.gif) |

さらに、View hierarchyでSafe Area Insetsを確認しても、`[]`の状態でした。
![スクロールあり修正後View hierarchy - Simulator - iPhone 14 Pro - iOS 16.2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/26278797-247b-2c0d-fcb1-488734f35ba4.png)

記事に記載されているコードを使い、無事、問題を解決できました❗️
実際に使ったコードはこちらになります。

[UIHostingControllerExtension.swift](https://github.com/MTattin/SafeAreaInsetsDemo/blob/main/SafeAreaInsetsDemo/ViewControllers/ChildViewController/ViewControllerIncludingSwiftUI/UIHostingControllerExtension.swift)


# 最後に

今回の問題は、`UIHostingController`に関連するので、フルSwiftUIのアプリには関係ないと思います。
ただ、私のように、一部のビューからSwiftUIを使っていこうと思っている方には役に立つ情報だと思いましたので、社内向けに情報共有を行うのではなく、Qiitaへ記事を投稿しました。

あまり遭遇しない事象かもしれませんが、同じような問題に遭遇して解決方法を探している方のお役に立てば幸いです。

本記事を書くために、サンプルプロジェクトを作成しましたので、GitHubで公開しました。
手元で試したい方はどうぞ。

[SafeAreaInsetsDemo](https://github.com/MTattin/SafeAreaInsetsDemo)

# 参考記事
- https://defagos.github.io/swiftui_collection_part3/#fixing-cell-frames
- https://stackoverflow.com/questions/61552497/uitableviewheaderfooterview-with-swiftui-content-getting-automatic-safe-area-ins
- https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets


# 追記（2023年4月22日）

会社の方に、iOS 16.4から`UIHostingController`に`safeAreaRegions`というプロパティが増え、それを使うことで`safeAreaInsets`の問題を解決できるようになっているという情報を教えて頂きました。

実際にXcodeで確認をしてみると、確かにiOS 16.4から使えるようになっていました。
![スクリーンショット 2023-04-22 18.15.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/4b1b0993-5f11-4730-0ab2-d42da3166c08.png)
「`The default value is SafeAreaRegions.all.`」ということなので、デフォルトの値をRemoveすることで`safeAreaInsets`の問題を解決することが出来ました。

|　Simulator - iOS 16.4 - safeAreaRegions を使用 |
|-----|
| ![useSafeAreaRegions.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30834/763f344d-7255-e577-39ad-b851b458d31d.gif) |

修正後のコードもアップしていますので動作確認したい方はどうぞ。
[SafeAreaInsetsDemo Ver 0.0.2](https://github.com/MTattin/SafeAreaInsetsDemo/releases/tag/0.0.2)

safearearegionsのドキュメントはこちらになります。
https://developer.apple.com/documentation/swiftui/uihostingcontroller/safearearegions