BGMアプリ開発記録簿①

こんちにちは。今日はWebViewを使用した開発記録をつけていきたいと思います。
私は自分のECサイトを運営しているのですがそのECサイトとアプリを連携させたいというのが今回の狙いです。

基本的な機能は、例えばボタンなどはWeb内において作成しているのでとても簡単にアプリの大まかな機能は作成することができるのがメリットです。ただしあまりにもアプリとしての機能が単純で、単にWebViewを貼り付けただけのアプリだったらApStoreにリリースする際にリジェクトされるので注意が必要です。

今回もChatGPTにSwiftUI開発用のコマンドを打ち込みそれをもとに質問しながら学習を開始します。


<table>
  <caption>開発環境</caption>
  <tr>
    <td> フレームワーク </td> <td>ChatGPT</td>
  </tr>
  <tr>
    <td> SwiftUI </td> <td>browsing</td>
  </tr>
</table>


でははじめていきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2138087/343c4c4d-12ba-fa93-f9b4-5e4b737c6bee.png)

返ってきた答えがこちら
```swift
import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    let url: URL

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

    func updateUIView(_ uiView: WKWebView, context: Context) {
        let request = URLRequest(url: url)
        uiView.load(request)
    }
}

struct ContentView: View {
    var body: some View {
        WebView(url: URL(string: "https://www.example.com")!)
    }
}

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

> まず、import SwiftUIとimport WebKitを使用して必要なモジュールをインポートします。次に、WebViewというSwiftUIのビューを作成し、その中にWKWebViewを埋め込みます。最後に、UIViewRepresentableプロトコルを適用して、UIKitのWKWebViewをSwiftUIで使用できるようにします。


ほうほう。まあこれで単純にWebを表示することができるということですね。
それでは表示の仕方がだいたいわかったので設計を含めて書き直してもらおうと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2138087/7078658b-53f6-bfab-8ad2-8d9cd10bba41.png)

するとContentView,WebView,WebViewLoaderという3つのファイルを作成してくれました


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

final class WebViewLoader: NSObject, WKNavigationDelegate {
    @Published var isLoading: Bool = true
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        isLoading = false
    }
    
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        isLoading = false
    }
}
```


### WebView

```swift
import SwiftUI

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

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

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


### ContentView

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

    var body: some View {
        VStack {
            if loader.isLoading {
                ProgressView()
                    .progressViewStyle(CircularProgressViewStyle())
                    .scaleEffect(2)
            }
            WebView(url: url, loader: loader)
        }
    }
}

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

>このように、WebViewLoader、WebView、およびContentViewを分離することで、各部分の役割が明確になり、コードがより読みやすく、メンテナンスが容易になります。また、Combineフレームワークを使用することで、データのフローと状態の管理がより簡潔で効果的になります。

ここでビルドしてみると

:::note alert
Generic struct 'ObservedObject' requires that 'WebViewLoader' conform to 'ObservableObject'
:::

という警告が出ました
>エラーメッセージは、WebViewLoaderがObservableObjectプロトコルに準拠していないと言っています。ObservableObjectプロトコルは、オブジェクトが変更を発行できるようにするためのもので、ObservedObjectプロパティラッパーと共に使用します。
あなたのWebViewLoaderクラスにObservableObjectプロトコルを追加することで、この問題を解決することができます。以下に修正したWebViewLoaderクラスを示します。

修正　ついでにファイル名も変更

```swift
import Combine
import WebKit

final class WebViewLoader: NSObject, WKNavigationDelegate, ObservableObject {
    @Published var isLoading: Bool = true
    @Published var error: Error?

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

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

```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
        return webView
    }

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

```swift
import SwiftUI

struct MainView: View {
    let url: URL
    @ObservedObject private var loader = WebViewLoader()

    var body: some View {
        VStack {
            if loader.isLoading {
                ProgressView()
                    .progressViewStyle(CircularProgressViewStyle())
                    .scaleEffect(2)
            } else if let error = loader.error {
                Text("An error occurred: \(error.localizedDescription)")
            } else {
                WebView(url: url, loader: loader)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        MainView(url: URL(string: "https://otostation1969.com")!)
    }
}
```

MainViewをさらに修正
```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 {
                ProgressView()
                    .progressViewStyle(CircularProgressViewStyle())
                    .scaleEffect(2)
            }
        }
    }
}
```

URLを定数にいれ、ProgressViewを中央に来るように修正（すいません、Progress
Viewも途中で作ってもらいました）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2138087/738df64f-cb1e-6eb3-d291-9cd113e2b783.png)


>Assistant（ゆるふわ先生）：それは素晴らしいアイデアですね、ちぬたさん！ローディング画面を見ているだけで退屈することはありますよね。それを楽しい経験に変えると、ユーザーエクスペリエンスが大幅に向上します。
ローディング画面に色を追加し、アニメーションを適用することで視覚的に魅力的なものにすることができます。以下にその一例を示します。

```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))
            }
        }
    }
}
```

>上記のコードでは、ローディング中に全画面を薄い青色で覆うRectangleを追加し、ProgressViewに無限に繰り返す回転アニメーションを適用しています。
これにより、ローディング画面がより視覚的に魅力的になり、ユーザーが待つ時間を楽しむことができます。


ここで一旦実行。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2138087/283770f7-eebc-744d-6210-14fc0051a058.png">

うん。画像にはありませんが、ProgressViewもういい感じでした。

今日は一旦ここまで。