SwiftUI如何在WKWebView中添加一个活动指示器?

8

如何在WKWebView中添加一个活动指示器,在网页加载时显示该指示器,并在加载完成后消失?

我查看了一些旧帖子,但无法弄清楚如何在SwiftUI中实现 请参见下面旧解决方案的链接 如何向WKWebView添加活动指示器(Swift 3)


这应该会有所帮助:https://dev59.com/tFMI5IYBdhLWcg3wUp02。UIActivityIndicator在SwiftUI中尚不是本地视图,因此您可能需要将其转换为本地视图,然后将其添加到您的视图中。 - palme
@palme 我该如何在WKWebview加载完成后关闭活动指示器? - Ashley Vaz
1
WKWebView有一个navigationDelegate,它在加载完成后调用func webView(WKWebView, didFinish: WKNavigation!)。在这里,您可以使用UIActivityIndicatorstopAnimating()方法。 - palme
2个回答

12

使用UIViewRepresentable来创建一个UIActivityIndicatorView

通过调用startAnimating()stopAnimating()方法来控制活动指示器的动画。要在动画停止时自动隐藏活动指示器,请将hidesWhenStopped属性设置为true。

可以使用color属性来设置活动指示器的颜色。

struct ActivityIndicatorView: UIViewRepresentable {
    @Binding var isAnimating: Bool
    let style: UIActivityIndicatorView.Style
    
    func makeUIView(context: UIViewRepresentableContext<ActivityIndicatorView>) -> UIActivityIndicatorView {
        return UIActivityIndicatorView(style: style)
    }
    
    func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicatorView>) {
        isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
    }
}
创建一个LoadingView以便您可以将其包装在视图周围:
这样可以使您样式化活动视图的内容。
struct LoadingView<Content>: View where Content: View {
    @Binding var isShowing: Bool
    var content: () -> Content
    
    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .center) {
                self.content()
                    .disabled(self.isShowing)
                    .blur(radius: self.isShowing ? 3 : 0)
                
                VStack {
                    Text("Loading...")
                    ActivityIndicatorView(isAnimating: .constant(true), style: .large)
                }
                .frame(width: geometry.size.width / 2, height: geometry.size.height / 5)
                .background(Color.secondary.colorInvert())
                .foregroundColor(Color.red)
                .cornerRadius(20)
                .opacity(self.isShowing ? 1 : 0)
                
            }
        }
    }
}

如果您希望能够更新 LoadingView(...) 的状态,则需要引入一个从 ObservableObject 继承的视图模型:

根据这个答案:https://stackoverflow.com/a/58825642/264802

class WebViewModel: ObservableObject {
    @Published var url: String
    @Published var isLoading: Bool = true
    
    init (url: String) {
        self.url = url
    }
}

struct WebView: UIViewRepresentable {
    @ObservedObject var viewModel: WebViewModel
    let webView = WKWebView()

    func makeCoordinator() -> Coordinator {
        Coordinator(self.viewModel)
    }
    
    class Coordinator: NSObject, WKNavigationDelegate {
        private var viewModel: WebViewModel
        
        init(_ viewModel: WebViewModel) {
            self.viewModel = viewModel
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            self.viewModel.isLoading = false
        }
    }
    
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<WebView>) { }
    
    func makeUIView(context: Context) -> UIView {
        self.webView.navigationDelegate = context.coordinator

        if let url = URL(string: self.viewModel.url) {
            self.webView.load(URLRequest(url: url))
        }

        return self.webView
    }
}

那么,在您的视图中使用它,您需要执行以下操作:

struct ContentView: View {
    @StateObject var model = WebViewModel(url: "http://www.google.com")

    var body: some View {
        LoadingView(isShowing: self.$model.isLoading) {
            WebView(viewModel: self.model)
        }
    }
}

WKWebview加载完成后,我该如何关闭活动指示器? - Ashley Vaz
@AshleyVaz 我更新了我的答案,向您展示如何在 WKWebview 加载完成后关闭活动指示器。 - fulvio
@AshleyVaz 这个回答解决了你的问题吗? - fulvio
非常感谢,非常感激,是的,这很有帮助。 - Ashley Vaz

-1

在我的项目中,我使用三个步骤来完成它。

第一步:创建一个加载视图

import SwiftUI
import UIKit

struct ActivityIndicatorView: UIViewRepresentable {
    @Binding var isAnimating: Bool
    let style: UIActivityIndicatorView.Style

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

    func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {
        isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
    }
}

// Main View
struct LoadingView<Content>: View where Content: View {
    @Binding var isShowing: Bool
    let message: String
    var content: () -> Content

    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .center) {
                self.content()
                    .disabled(self.isShowing)
                    .blur(radius: self.isShowing ? 3 : 0)

                VStack {
                    Text(self.message)
                        .bold()
                    ActivityIndicatorView(isAnimating: .constant(true), style: .large)
                }
                .frame(width: geometry.size.width / 2,
                       height: geometry.size.height / 5)
                .background(Color.secondary.colorInvert())
                .foregroundColor(Color.primary)
                .cornerRadius(20)
                .opacity(self.isShowing ? 1 : 0)
            }
        }
    }

}

// Mark: Testing

struct LoadingIndicator: View {
    var body: some View {
        LoadingView(isShowing: .constant(true), message: "Loading...") {
            NavigationView {
                List(["1", "2", "3", "4", "5"], id: \.self) { row in
                    Text(row)
                }.navigationBarTitle(Text("A List"), displayMode: .large)
            }
        }
    }
}

struct ActivityIndicatorView_Previews: PreviewProvider {
    static var previews: some View {
        LoadingIndicator()
    }
}

步骤2:创建 WebView 和 WebViewModel

import SwiftUI
import WebKit

class WebViewModel: ObservableObject {
    @Published var isLoading: Bool = false
}

struct WebView: UIViewRepresentable {
    @ObservedObject var webViewModel: WebViewModel
    let urlString: String

    func makeUIView(context: Context) -> WKWebView {
        let wkWebView = WKWebView()
        if let url = URL(string: urlString) {
            let urlRequest = URLRequest(url: url)
            wkWebView.load(urlRequest)
        }
        return wkWebView
    }

    func updateUIView(_ wkWebView: WKWebView, context: Context) {
        // do nothing
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        let webViewModel: WebViewModel

        init(_ webViewModel: WebViewModel) {
            self.webViewModel = webViewModel
        }

        func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            webViewModel.isLoading = true
        }

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

    func makeCoordinator() -> WebView.Coordinator {
        Coordinator(webViewModel)
    }
}

struct WebView_Previews: PreviewProvider {
    static var previews: some View {
        WebView(webViewModel: WebViewModel(),
                urlString: "https://instagram.com/mahmudahsan/")
    }
}

步骤3:在您的主视图中使用以下代码来显示指示器和Web视图

ZStack {
        WebView(webViewModel: webViewModel, urlString: "http://ithinkdiff.net")
            .frame(height: 1000)

        if webViewModel.isLoading {
              LoadingView(isShowing: .constant(true), message: "Loading...") {
                            EmptyView()
              }
        }
}

谢谢@Mahmud Ahsan,视图加载了网页,但在页面完全加载后仍然显示活动指示器,有什么想法? - Ashley Vaz
2
我在协调器类上添加了一个断点,但似乎控制流程从未到达协调器中的 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.webViewModel.isLoading = false } - Ashley Vaz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接