[SwiftUI] UIViewController を引数に取る関数を使う方法

## 概要

実行時に `UIViewController` を必要とする関数は少なくありません。

- `GoogleSignIn` ライブラリの `GIDSignIn.sharedInstance.signIn(with: , presenting: )`
- AdMob のインタースティシャル広告表示時の `GADInterstitialAd.present(fromRootViewController: )` 

`UIViewController` が表立って使うことがない SwiftUI において、それらの関数を実行する際に `UIApplication.shared.windows.first?.rootViewController` を使う方法を紹介している記事が数多くあります。

しかし、これはあまり安全とは言えません。

https://zenn.dev/matsuji/articles/0ee306ddfd10dc

今回は、`UIApplication.shared.windows.first?.rootViewController` を使わずにこれらの関数を使用する方法を紹介したいと思います。

## 参考記事

https://ads-developers.googleblog.com/2022/11/swiftui-case-study-presenting-from-view.html

## ざっくりとした流れ

ちょっとトリッキーですが、やり方はそこそこシンプルです

1. 空の `UIViewController` を `UIViewControllerRepresentable` でラップした `View` を作成
1. SwiftUI の View に、前述の `View` を初期化した変数 (仮に `anchorView` とする) を追加
1. `body` 内で、`.background` に前述の変数を設置
1. `UIViewController` を必要とする関数に、`anchorView.viewController` を渡す

## 実際に書いてみる

- ここでは、例として AdMob のインタースティシャル広告を表示するコードを書いてみたいと思います。

1. 空の `UIViewController` をラップした `RepresentedEmptyViewController` を作成


    ```swift
    import SwiftUI

    // MARK: - 空の `UIViewController` をラップしたもの
    struct RepresentedEmptyViewController: UIViewControllerRepresentable {
        let viewController: UIViewController = .init()

        func makeUIViewController(context: Context) -> some UIViewController {
            return self.viewController
        }

        func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        }
    }
    ```

2. `UIViewController` を必要とする関数を実行する View に、前述の `RepresentedEmptyViewController` を初期化した変数を追加


    ```swift
    import SwiftUI

    struct ContentView: View {
        // これ↓
        let prensentationAnchorView: RepresentedEmptyViewController = .init()

        var body: some View {
        // 中略
        }
    }
    ```

3. `body` 内のルートのビューに、`.background` モディファイアを付与し、前述の `prensentationAnchorView` を設置

    - background に設置することで、UIへの影響なく設置できます

    ```swift
    import SwiftUI

    struct ContentView: View {
        let prensentationAnchorView: RepresentedEmptyViewController = .init()

        var body: some View {
            VStack {
                
            }
            // これ↓
            .background {
                prensentationAnchorView
            }
        }
    }
    ```

4. `UIViewController` を必要とする関数を使う処理を書く (ここでは `GADInterstitialAd.present(fromRootViewController: )`)

    ```swift
    import SwiftUI
    import GoogleMobileAds

    struct ContentView: View {
        @State var interstitialAdObject: GADInterstitialAd? = nil

        let prensentationAnchorView: RepresentedEmptyViewController = .init()

        func loadAd() async {
            do {
                self.interstitialAdObject = try await GADInterstitialAd.load(withAdUnitID: "ユニットID", request: GADRequest())
            } catch {
                print(error)
            }
        }

        func showInterstitialAd() {
            self.interstitialAdObject.present(fromRootViewController: prensentationAnchorView.viewController)
        }

        var body: some View {
            VStack {
                Button(action: showInterstitialAd) {
                    Text("広告を表示")
                }
            }
            .task {
                await loadAd()
            }
            .background {
                prensentationAnchorView
            }
        }
    }
    ```

こうすることで、`Button` 押下時にインタースティシャル広告が表示されるようになります。


`presentationAnchorView` を起点として、モーダルが表示されるイメージですね。