BGMアプリ開発記録簿②

前回からの続きです。
アプリを実行して気づいたのですがブラウザの戻るや進むボタンがなかったのでとても操作性が悪かったのでボタンを作ってもらおうと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2138087/ca723848-1f3f-fa6a-913f-362ccf6a585b.png)

```swift
struct MainView: View {
    private let url = URL(string: "https://otostation1969.com")!
    @ObservedObject private var loader = WebViewLoader()

    var body: some View {
        ZStack {
            WebView(url: url, loader: loader)
            if loader.isLoading {
                Rectangle()
                    .fill(Color.blue.opacity(0.2))
                    .edgesIgnoringSafeArea(.all)
                ProgressView()
                    .progressViewStyle(CircularProgressViewStyle())
                    .scaleEffect(2)
                    .rotationEffect(Angle(degrees: loader.isLoading ? 360 : 0))
                    .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
            }
        }
        Toolbar {
            Button(action: {
                loader.webView.goBack()
            }) {
                Image(systemName: "arrow.backward")
            }
            .disabled(!loader.webView.canGoBack)

            Button(action: {
                loader.webView.goForward()
            }) {
                Image(systemName: "arrow.forward")
            }
            .disabled(!loader.webView.canGoForward)
        }
    }
}
```
>このコードでは、SwiftUIのToolbarを使って、ブラウザの「戻る」ボタンと「進む」ボタンを追加しています。ボタンのアイコンには、システムのアイコンを使用しています。
また、WKWebViewのcanGoBackとcanGoForwardプロパティを使用して、戻るボタンと進むボタンを適切に無効化しています。
ただし、このコードは一例であり、実際のWebViewLoaderクラスがwebViewプロパティを公開していない場合は、適切なメソッドをWebViewLoaderに追加する必要があります。そのメソッドは内部のWKWebViewに対してgoBack()やgoForward()を呼び出します。
また、このコードはSwiftUI 2.0以上で動作します。それ以前のバージョンを使用している場合は、Toolbar APIが存在しないため、代わりに自分でボタングループを作成する必要があります。



こちらのコードが帰ってきましたので実行してきますすると


:::note alert
Cannot find 'Toolbar' in scope
:::

修正します。

```swift
struct MainView: View {
    private let url = URL(string: "https://otostation1969.com")!
    @ObservedObject private var loader = WebViewLoader()

    var body: some View {
        ZStack {
            WebView(url: url, loader: loader)
            if loader.isLoading {
                Rectangle()
                    .fill(Color.blue.opacity(0.2))
                    .edgesIgnoringSafeArea(.all)
                ProgressView()
                    .progressViewStyle(CircularProgressViewStyle())
                    .scaleEffect(2)
                    .rotationEffect(Angle(degrees: loader.isLoading ? 360 : 0))
                    .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
            }
        }
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                HStack {
                    Button(action: {
                        loader.webView.goBack()
                    }) {
                        Image(systemName: "arrow.backward")
                    }
                    .disabled(!loader.webView.canGoBack)

                    Button(action: {
                        loader.webView.goForward()
                    }) {
                        Image(systemName: "arrow.forward")
                    }
                    .disabled(!loader.webView.canGoForward)
                }
            }
        }
    }
}
```

少しのミスはありますがこのように一つずつ修正していけばエラーは解消していきます。

:::note alert
Reference to member 'goBack' cannot be resolved without a contextual type
Reference to member 'canGoBack' cannot be resolved without a contextual type
:::

次にこのエラーが発生しました。
>エラーメッセージは「コンテキストを持たない型では、メンバー 'goBack' および 'canGoBack' の解決はできません」と言っています。これは、特定のメンバー（この場合は goBack と canGoBack）を参照しようとしているが、それがどの型に属しているのか、コンパイラが判断できないということを意味します。
これは通常、呼び出し側が期待する型が定義側と一致していない場合や、型が明示的に指定されていない場合に発生します。
ご提示のコードを見ると、MainView の toolbar 内の Button の action で loader.webView.goBack() と loader.webView.canGoBack を呼び出そうとしていますが、loader.webView の型 (WKWebView?) が Optional であるために発生していると思われます。そのため、Optional をアンラップする必要があります。

```swift
.toolbar {
    ToolbarItem(placement: .bottomBar) {
        HStack {
            Button(action: {
                loader.goBack()
            }) {
                Image(systemName: "arrow.backward")
            }
            .disabled(!loader.canGoBack)

            Button(action: {
                loader.goForward()
            }) {
                Image(systemName: "arrow.forward")
            }
            .disabled(!loader.canGoForward)
        }
    }
}
```

このように帰ってきました。他にも微妙な修正を会話形式で直していき最終的にはこのようなコードになりました。全てを記録したかったのですが、途中でチャットが止まったり新規チャットで質問し直したり、ブラウジング機能をオンにしたりキャンセルしたりしてこの答えにたどり着きました。ChatCPTはこのように会話形式で少しずつ修正をかけていくことが大切です。一度に多くの質問するのではなく少しずつ質問しまた質問の前に明確な行動を示してから質問するなどといった工夫をすると期待した答えが得られることが多いです。

### 最終的なコード

#### MainView
```swift
import SwiftUI

struct MainView: View {
    private let url = URL(string: "https://otostation1969.com")!
    @ObservedObject private var loader = WebViewLoader()

    var body: some View {
        ZStack {
            WebView(url: url, loader: loader)
            if loader.isLoading {
                Rectangle()
                    .fill(Color.blue.opacity(0.2))
                    .edgesIgnoringSafeArea(.all)
                ProgressView()
                    .progressViewStyle(CircularProgressViewStyle())
                    .scaleEffect(2)
                    .rotationEffect(Angle(degrees: loader.isLoading ? 360 : 0))
                    .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
            }
        }
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                HStack {
                    Button(action: {
                        loader.goBack()
                    }) {
                        Image(systemName: "arrow.backward")
                    }
                    .disabled(!loader.canGoBack)

                    Button(action: {
                        loader.goForward()
                    }) {
                        Image(systemName: "arrow.forward")
                    }
                    .disabled(!loader.canGoForward)
                }
            }
        }
    }
}



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

#### WebViewLoader
```swift
import Combine
import WebKit

final class WebViewLoader: NSObject, WKNavigationDelegate, ObservableObject {
    @Published var isLoading: Bool = true
    private var webView: WKWebView? = nil

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        isLoading = false
    }

    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        isLoading = false
    }

    func attachWebView(_ webView: WKWebView) {
        self.webView = webView
    }

    func goBack() {
        webView?.goBack()
    }

    func goForward() {
        webView?.goForward()
    }

    var canGoBack: Bool {
        webView?.canGoBack ?? false
    }

    var canGoForward: Bool {
        webView?.canGoForward ?? false
    }
}
```

#### WebView
```swift
import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    let url: URL
    @ObservedObject var loader: WebViewLoader

    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        webView.navigationDelegate = loader
        loader.attachWebView(webView)
        return webView
    }

    func updateUIView(_ webView: WKWebView, context: Context) {
        if webView.url == nil {
            webView.load(URLRequest(url: url))
        }
    }
}
```

### 実行
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2138087/df00dc44-5614-a591-74ad-b487caf8cf07.png
">

一応もどる、進むボタンは実装できたので次回はもう少しツールバーのデザインなどに変更を加えたいと思いますまたナビゲーションバーをつけるかどうかも検討したいと思います。