「The Ultimate Guide to WKWebView」をSwiftUIで実装する #04 - Loading HTML fragments -

[「The Ultimate Guide to WKWebView」](https://www.hackingwithswift.com/articles/112/the-ultimate-guide-to-wkwebvie)をSwiftUIで実装してみるの、
４つ目になります。

## 目次

シリーズ化していこうと思うので、全体の目次を置いておきます。
リンクが貼られていないタイトルは、記事作成中または未作成のものになります。

|#|タイトル|
|------|-----------------|
|01|[Making a web view fill the screen](https://qiita.com/kamimi01/items/b4d6cbafa587831c1be0) <br> （WebViewを画面に表示する）|
|02|[Loading remote content](https://qiita.com/kamimi01/items/c889979964820d5f9fa2) <br> （リモートのコンテンツを読み込む）|
|03|[Loading local content](https://qiita.com/kamimi01/items/9cefe1e73ab5b88051c6) <br> （ローカルのコンテンツを読み込む）|
|04|[Loading HTML fragments](https://qiita.com/kamimi01/items/2f267b328f3f6158bc26) <br> （HTMLフラグメントの読み込み）|
|05|[Controlling which sites can be visited](https://qiita.com/kamimi01/items/7c01aed497108da35b5a) <br> （訪問可能なサイトの制御）|
|06|[Opening a link in the external browser](https://qiita.com/kamimi01/items/716dccb6f109ee50c900) <br> （外部ブラウザでリンクを開く）|
|07|[Monitoring page loads](https://qiita.com/kamimi01/items/33446e032822518cb7f7) <br> （ページの読み込みを監視する）|
|08|[Reading a web page’s title as it changes](https://qiita.com/kamimi01/items/7e0153ee68474a155b9a) <br> （Webページのタイトルの変化を読み取る）|
|09|[Reading pages the user has visited](https://qiita.com/kamimi01/items/f5f6c35a6a68726fa485) <br> （ユーザーが閲覧したページを読み取る）|
|10|[Injecting JavaScript into a page](https://qiita.com/kamimi01/items/6076081dc54e2b3d0abf) <br> （JavaScriptをページに注入する）|
|11|[Reading and deleting cookies](https://qiita.com/kamimi01/items/90139fe692ec988d28bb) <br> （cookieの読み取りと削除）|
|12|[Providing a custom user agent](https://qiita.com/kamimi01/items/5884330e315338df8ec0) <br> （カスタムUser Agentを提供する）|
|13|[Showing custom UI ](https://qiita.com/kamimi01/items/3315e4df0813eda75eeb)<br> （カスタムUIを表示する）|
|14|Snapshot part of the page <br> （ページの一部のスナップショットを撮る）|
|15|Detecting data <br> （データの探索）|

## 環境

【Xcode】13.1
【Swift】5.5
【iOS】15.0
【macOS】Big Sur バージョン 11.4

## 実現したいこと

今回やることは
TextFieldに入力した文字列を元にHTML文字列を作成して表示します。

TextFieldになんも入力されてない時は、「nothing」の文字列が入るようにしました。
I love nothingってちょっと悲しい感じだけど・・・ :sweat:

またHTMLは、アプリバンドル内にあるCSSを読み込んでいます。
Hacking with Swiftの例より少しだけ発展版になっています。

今回の成果物は、こんな感じになりました。

![gif](https://user-images.githubusercontent.com/47489629/152683105-b6423409-2a77-429f-9659-31645f9a07fd.gif)

## 実現方法

まずWebViewです。

最初に`inputText`を使って、HTML文字列を作成します。
次に、そのHTMLに適用するCSSをアプリバンドルから読み込み、`loadHTMLString(_:baseURL:)`の`baseURL`に渡します。

ちなみに`baseURL`は

「HTML文字列の中にある、相対URLを解決する時に使用するURL」とのこと。

> The base URL to use when the system resolves relative URLs within the HTML string.

今回のこのHTMLでは、`<link rel="stylesheet" type="text/css" href="index.css">`とあるように、CSSを適用しますので
その相対URLを取得して`baseURL`に渡しているわけです。

```swift:WebView.swift
import Foundation
import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    let inputText: String

    func makeUIView(context: Context) -> WKWebView {
        return WKWebView()
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        let displayedText = inputText.isEmpty ? "nothing" : inputText

        let html = """
        <html>
        <head>
        <link rel="stylesheet" type="text/css" href="index.css">
        </head>
        <body>
        <h1>Hello World!</h1>
        <h2>I am kamimi. I love \(displayedText).</h2>
        </body>
        </html>
        """
        
        guard let urlPath = Bundle.main.path(forResource: "index", ofType: "css") else {
            return
        }
        let url = NSURL.fileURL(withPath: urlPath)
        uiView.loadHTMLString(html, baseURL: url)
    }
}
```

ちなみに適用したCSSはこれ。
h1タグはオレンジに、h2タグは青になります。

```css:index.css
h1 {
  color: orange;
  font-size: 100px;
}

h2 {
  color: blue;
  font-size: 80px;
}
```

Hacking with Swiftの例では、あまり`baseURL`の使い方は説明されてなかったので、試しに使ってみました。

またその例では、このHTMLをただ表示しているだけだったのですが、

```html
<html>
<body>
<h1>Hello, Swift!</h1>
</body>
</html>
```

`loadHTMLString(_:baseURL:)`のAppleのドキュメントを見たところ、Discussionに

「例えば、アプリがプログラムで生成したHTMLコンテンツを読み込むために使う」

> For example, you might use this method to load HTML content that your app generates programmatically.

とあったので、TextFieldに入力された文字列をHTML文字列の中に埋め込む、というそれっぽい方法でやってみました。

ちなみに、仕事でこのメソッドを使用した時は、
APIのレスポンスの一部がこんな感じのHTML文字列だったので、それをWebViewで表示したいというユースケースでした。

```json
{
  "content": "<html><body><h1>Hello, Swift!</h1></body></html>"
}
```

このHTML文字列の場合であれば、`baseURL`は`nil`で良さそうですね。
特に相対パス埋め込まれていませんし。

---

では次に、WebViewを表示するViewです。

TextFieldが１つと、WebViewを開くためのボタンが１つあります。
開いたWebViewは、左上に閉じるボタンをつけました。



```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var isShownWebView = false
    @State private var inputText = ""

    var body: some View {
        VStack {
            TextField("", text: $inputText)
                .border(.yellow)
                .padding()
            Button(action: {
                isShownWebView.toggle()
            }) {
                Text("WebView開く")
            }
        }
        .fullScreenCover(isPresented: $isShownWebView) {
            webBaseView
        }
    }
}

private extension ContentView {
    var webBaseView: some View {
        NavigationView {
            WebView(inputText: inputText)
                .navigationBarItems(leading: closeButton)
        }
    }

    var closeButton: some View {
        Button(action: {
            isShownWebView.toggle()
        }) {
            Text("閉じる")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

以上です。

コード全体はこちらにあげています。

https://github.com/kamimi01/TheUltimateGuidetoWKWebViewWithSwiftUI/tree/main/LoadingHTMLFragments

どうでも良い個人的な話なのですが、
Swiftも書きながら、HTML、CSSも書いてーとか色々触るの楽しいですね。:blush:


## 参考

- [[Swift] WKWebViewでローカルのフォントを読み込む方法](https://gekkado.com/archives/856)
- [`loadHTMLString(_:baseURL:)`Apple公式ドキュメント](https://developer.apple.com/documentation/webkit/wkwebview/1415004-loadhtmlstring)