こんちにちは。今日はWebViewを使用した開発記録をつけていきたいと思います。
私は自分のECサイトを運営しているのですがそのECサイトとアプリを連携させたいというのが今回の狙いです。
基本的な機能は、例えばボタンなどはWeb内において作成しているのでとても簡単にアプリの大まかな機能は作成することができるのがメリットです。ただしあまりにもアプリとしての機能が単純で、単にWebViewを貼り付けただけのアプリだったらApStoreにリリースする際にリジェクトされるので注意が必要です。
今回もChatGPTにSwiftUI開発用のコマンドを打ち込みそれをもとに質問しながら学習を開始します。
フレームワーク | ChatGPT |
SwiftUI | browsing |
返ってきた答えがこちら
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を表示することができるということですね。
それでは表示の仕方がだいたいわかったので設計を含めて書き直してもらおうと思います。
するとContentView,WebView,WebViewLoaderという3つのファイルを作成してくれました
WebViewLoader
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
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
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フレームワークを使用することで、データのフローと状態の管理がより簡潔で効果的になります。
ここでビルドしてみると
Generic struct 'ObservedObject' requires that 'WebViewLoader' conform to 'ObservableObject'
という警告が出ました
エラーメッセージは、WebViewLoaderがObservableObjectプロトコルに準拠していないと言っています。ObservableObjectプロトコルは、オブジェクトが変更を発行できるようにするためのもので、ObservedObjectプロパティラッパーと共に使用します。
あなたのWebViewLoaderクラスにObservableObjectプロトコルを追加することで、この問題を解決することができます。以下に修正したWebViewLoaderクラスを示します。
修正 ついでにファイル名も変更
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
}
}
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))
}
}
}
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をさらに修正
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も途中で作ってもらいました)
Assistant(ゆるふわ先生):それは素晴らしいアイデアですね、ちぬたさん!ローディング画面を見ているだけで退屈することはありますよね。それを楽しい経験に変えると、ユーザーエクスペリエンスが大幅に向上します。
ローディング画面に色を追加し、アニメーションを適用することで視覚的に魅力的なものにすることができます。以下にその一例を示します。
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に無限に繰り返す回転アニメーションを適用しています。
これにより、ローディング画面がより視覚的に魅力的になり、ユーザーが待つ時間を楽しむことができます。
うん。画像にはありませんが、ProgressViewもういい感じでした。
今日は一旦ここまで。