「The Ultimate Guide to WKWebView」をSwiftUIで実装する #08 - Reading a web page’s title as it changes -

[「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

## 実現したいこと

今回やることは
読み込んでいるページのタイトルを取得して、Navigation Barのタイトルとして表示する
これだけです。

![app.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401966/599565b4-c330-5439-129f-91c1ad52ee62.gif)

（また下が黒い現象が・・・もう気にしない）

## 実現方法

まずWebViewです。

```swift
import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    let url: URL
    @Binding var title: String

    private let webView = WKWebView()

    func makeUIView(context: Context) -> WKWebView {
        webView.navigationDelegate = context.coordinator
        let request = URLRequest(url: url)
        webView.load(request)
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
}

extension WebView {
    class Coordinator: NSObject {
        var parent: WebView

        var observations: [NSKeyValueObservation?] = []

        init(_ parent: WebView) {
            self.parent = parent

            // タイトルが取得でき次第、NavigationBarのタイトルに反映する
            let titleObservation = parent.webView.observe(\.title, options: .new, changeHandler: { _, value in
                parent.title = (value.newValue ?? "") ?? ""
            })
            observations = [titleObservation]
        }
    }
}
```

今回は、[前回の記事](https://qiita.com/kamimi01/items/33446e032822518cb7f7)でも使ったKVOで、ページのタイトルを取得します。

前回KVOで監視していた値は、ページの読み込み率だったのですが、
今度はそれがページのタイトルになっただけです。

なのですが、実はタイトルの取得方法はもう一つあります。↓

```swift
// 読み込み完了時に呼ばれ、NavigationBarのタイトルに反映する
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    parent.title = webView.title ?? ""
}
```

それがこの`webView(_:didFinish:)`メソッドを使用した方法です。

「ナビゲーションが完了したことをDelegateに通知する」というものです。

> Tells the delegate that navigation is complete.

KVOでは、タイトルの値を常に監視しているので、タイトルに変化があり次第、ナビゲーションバーのタイトルにその値が反映されます。

対して、`webView(_:didFinish:)`メソッドを使った方法では、
ページの読み込みが完了した時点で、タイトルを取得し、ナビゲーションバーのタイトルにその値を反映します。

これからも想定できるように、KVOの方が取得と反映が早いです。

KVOの方法と`webView(_:didFinish:)`メソッドを使う方法の両方に、`print`を仕込んでどちらが早くタイトルの値を出力するか、５回ほど試したのですが、
5回中5回、KVOの方が早かったです。（体感1,2秒ほど早い）

まあ確かめるまでもなかったかもしれませんが。

いろんな記事を見ても、どっちの方法でもやられているので、どっちでもいいのかね。

ここはよくわからなかったのですが、
特に何かデメリットがなければ、より早く反映されるKVOの方でいいんじゃないかなーと思い
今回はそちらを採用しています。

あと、元記事のHacking with SwiftではKVOだったというのもあります。

また、`title`だけに限った話ではありませんが、`title`以外にも
KVOでも、`webView(_:didFinish:)`メソッドでも取れる値はあります。
（`canGoBack`とか。）

今回取得したかったタイトルは、正直どちらで取得してもそこまで大差なさそうなので、どっちでも良いかなと思った次第です。

---

次にWebView.swiftを使うViewの実装です。
今回はシンプルです。

```swift
import SwiftUI

struct ContentView: View {
    private let url = URL(string: "https://www.apple.com/")!
    @State private var title = ""

    var body: some View {
        NavigationView {
            WebView(url: url, title: $title)
                .navigationBarTitle(title, displayMode: .inline)
        }
    }
}
```

以上！

コード全体は以下に挙がっています。

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

前回と似た内容だったので、少し手抜き感あるかもですが、まあそういうこともある！（誰向け？笑）

## 参考

- [The Ultimate Guide to WKWebView](https://www.hackingwithswift.com/articles/112/the-ultimate-guide-to-wkwebvie)
- [`webView(_:didFinish:)`Apple公式ドキュメント](https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview)
- [Swiftとオブジェクト間の通知のパターン](https://qiita.com/shtnkgm/items/f07f1c61985214ceb7b3)