【Swift】Viewが生成されてからレイアウトしたい

# はじめに
Qiitaに書くほどではないとは思いますが、
UIKit初心者の自分からしたらどうやってやるの？ポイントだったので記事にしておきます

# やろうとしてること
① UIViewControllerでWKWebViewを表示します
② 作ったUIViewControllerをUIViewControllerRepresentableでViewにします
③ SwiftUI側でsheetで表示します

# 問題点
iPadの`sheet`は全画面ではないのでWebViewがはみ出る
|iPhone|iPad|
|:-:|:-:|
|![simulator_screenshot_C3E6D1A2-91C1-4992-A399-57FD25C93EA8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/59b4560f-a945-ddce-97b8-7598a9d76d07.png)|![simulator_screenshot_9881A23D-FBC7-43C2-831E-173F1E330CA2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/082b0340-4428-4ddf-da85-89a533786b81.png)|

# 修正前のコード
```swift:WebView
import UIKit
import WebKit
import SwiftUI

struct WebView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> WebViewController {
        return WebViewController()
    }
    func updateUIViewController(_ uiViewController: WebViewController, context: Context) { }
}

class WebViewController: UIViewController {

    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        webView = WKWebView(frame: view.frame)

        view.addSubview(webView)

        webView.customUserAgent = "Mozilla/5.0 (\(UIDevice.current.userInterfaceIdiom == .phone ? "iPhone" : "iPad"); CPU \(UIDevice.current.userInterfaceIdiom == .phone ? "iPhone" : "iPad") OS \(UIDevice.current.systemVersion.replacingOccurrences(of: ".", with: "_")) like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1"

        let url = URLRequest(url: URL(string: "https://mobile.twitter.com/i/flow/login")!)

        webView.load(url)
    }
}
```
```swift:ContentView
import SwiftUI

struct ContentView: View {
    @State var isWebView: Bool = false
    var body: some View {
        Button("ログイン画面表示") {
            isWebView = true
        }
        .sheet(isPresented: $isWebView) {
            WebView()
        }
    }
}
```

# 修正後のコード
```diff_swift:WebView
import UIKit
import WebKit
import SwiftUI

struct WebView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> WebViewController {
        return WebViewController()
    }
    func updateUIViewController(_ uiViewController: WebViewController, context: Context) { }
}

class WebViewController: UIViewController {

    var webView: WKWebView!

+   override func viewWillLayoutSubviews() {
+       webView.widthAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
+       webView.heightAnchor.constraint(equalToConstant: view.bounds.height).isActive = true
+   }

    override func viewDidLoad() {
        super.viewDidLoad()

-       webView = WKWebView(frame: view.frame)
+       webView = WKWebView(frame: .zero)

+       webView.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(webView)

        webView.customUserAgent = "Mozilla/5.0 (\(UIDevice.current.userInterfaceIdiom == .phone ? "iPhone" : "iPad"); CPU \(UIDevice.current.userInterfaceIdiom == .phone ? "iPhone" : "iPad") OS \(UIDevice.current.systemVersion.replacingOccurrences(of: ".", with: "_")) like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1"

        let url = URLRequest(url: URL(string: "https://mobile.twitter.com/i/flow/login")!)

        webView.load(url)
    }
}
```
```swift:ContentView
import SwiftUI

struct ContentView: View {
    @State var isWebView: Bool = false
    var body: some View {
        Button("ログイン画面表示") {
            isWebView = true
        }
        .sheet(isPresented: $isWebView) {
            WebView()
        }
    }
}
```
|iPhone|iPad|
|:-:|:-:|
|![simulator_screenshot_C3E6D1A2-91C1-4992-A399-57FD25C93EA8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/59b4560f-a945-ddce-97b8-7598a9d76d07.png)|![simulator_screenshot_9411B724-75B8-4BC2-8CD4-390CD4E2C03F.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/f87a3ef3-c13c-0bc0-9958-e780715edcdd.png)|

# 解説
![ViewControllerのライフサイクル.png](https://qiita-image-store.s3.amazonaws.com/0/45525/670d0038-6f03-095f-ca22-90c510f8babf.png)

https://qiita.com/motokiee/items/0ca628b4cc74c8c5599d

UIViewControllerのライフサイクルを理解していなかったのが原因だと思います。
`viewDidLoad`で`sheet`のframeがとれるわけが無いので今考えたら当然って感じがしますが笑

## 試したこと1
最初にやったのは`viewWillLayoutSubviews`でWebViewを生成してみました。
```swift
    override func viewWillLayoutSubviews() {
        webView = WKWebView(frame: view.frame)

        view.addSubview(webView)

        webView.customUserAgent = "Mozilla/5.0 (\(UIDevice.current.userInterfaceIdiom == .phone ? "iPhone" : "iPad"); CPU \(UIDevice.current.userInterfaceIdiom == .phone ? "iPhone" : "iPad") OS \(UIDevice.current.systemVersion.replacingOccurrences(of: ".", with: "_")) like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1"

        let url = URLRequest(url: URL(string: "https://mobile.twitter.com/i/flow/login")!)

        webView.load(url)
    }
```
これは見た目的にはいい感じになったのですが、
`viewWillLayoutSubviews`は画面をちょっと動かすと何度でも呼ばれるっぽく、
その度に再読み込みが発生してまともに使えませんでした。

## 試したこと2
1で再読み込みが発生するのは`viewWillLayoutSubviews`の中に`webView.load(url)`があるからだ！と思い、レイアウトが終わってすぐ`webView.load(url)`をしようとしました。
```swift
    override func viewWillLayoutSubviews() {
        webView = WKWebView(frame: view.frame)

        view.addSubview(webView)
    }

    override func viewDidAppear(_ animated: Bool) {
        webView.customUserAgent = "Mozilla/5.0 (\(UIDevice.current.userInterfaceIdiom == .phone ? "iPhone" : "iPad"); CPU \(UIDevice.current.userInterfaceIdiom == .phone ? "iPhone" : "iPad") OS \(UIDevice.current.systemVersion.replacingOccurrences(of: ".", with: "_")) like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1"

        let url = URLRequest(url: URL(string: "https://mobile.twitter.com/i/flow/login")!)

        webView.load(url)
    }
```
ライフサイクル表を見る限りこれは成功しそうです。
しかし、原因は分かりませんが、WebViewは真っ白で読み込みが行われませんでした。

# おわり
おそらくこれは基本中の基本だと思いますが、大切だと思ったのでQiitaに残しておきます。
UIKit初心者は同じ問題にぶち当たると思うのでこの記事が役に立ては嬉しいです。