如何在 swiftUI 中以线性样式 ProgressView 显示 WKWebView 的估计进度?

How to display estimatedProgress of WKWebView in a Linear style ProgressView in swiftUI?

我尝试就此事搜索数周,但找不到在 SwiftUI 中实现的任何帮助。大多数答案都是针对 UIKit 的。

struct ContentView: View {
    @State private var progress: Double = 0.0
    
    var body: some View {
        VStack {
            WebView()

            ProgressView(value: progress)
                .progressViewStyle(.linear)
        }
    }
}

struct WebView: UIViewRepresentable {
    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        webView.load(URLRequest(url: URL(string: "https://www.apple.com")!))
        return webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        
    }
}

因为您正在使用 UIViewRepresentable 来包装 WKWebView 您需要使用 UIKit 解决方案。

我们可以使用键值观察 (KVO) 跟踪 WKWebViewestimatedProgress。为此,我们需要向 WebView.

添加一个协调器

仅仅添加协调器是不够的,我们需要一种方法将值从 UIViewRepresentable 传回 View。我们可以使用 @Binding 或闭包来完成此操作,但这会在更新视图时导致以下错误:

Warning: Modifying state during view update, this will cause undefined behavior.

一个“hacky”解决方案是使用 DispatchQueue.main.async 包装绑定 progress 变量的设置,但这不是一个好的解决方案。

解决此问题的更好方法是创建一个符合 ObservableObject.

的简单 ViewModel

这给出了以下代码:

struct ContentView: View {
    @StateObject var viewModel = WebView.ProgressViewModel(progress: 0.0)

    var body: some View {
        VStack {
            WebView(url: URL(string: "https://www.apple.com")!, viewModel: viewModel)

            ProgressView(value: viewModel.progress)
                .progressViewStyle(.linear)
        }
    }
}

ContentView 现在持有对 ProgressViewModel 的引用,它与 URL 一起传递给 WebViewProgressViewProgressViewModel 传递 @Published 进度。

struct WebView: UIViewRepresentable {

    let url: URL
    @ObservedObject var viewModel: ProgressViewModel

    private let webView = WKWebView()

    func makeUIView(context: Context) -> WKWebView {
        webView.load(URLRequest(url: url))
        return webView
    }

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

    }
}

这与您的 UIViewRepresentable 没有太大区别,只是我们要传递 URLProgressViewModel。我们还从 makeUIView 中提出了 webView 的初始化,这样我们就可以在 Coordinator.

中访问它
extension WebView {

    func makeCoordinator() -> Coordinator {
        Coordinator(self, viewModel: viewModel)
    }

    class Coordinator: NSObject {
        private var parent: WebView
        private var viewModel: ProgressViewModel
        private var observer: NSKeyValueObservation?

        init(_ parent: WebView, viewModel: ProgressViewModel) {
            self.parent = parent
            self.viewModel = viewModel
            super.init()

            observer = self.parent.webView.observe(\.estimatedProgress) { [weak self] webView, _ in
                guard let self = self else { return }
                self.parent.viewModel.progress = webView.estimatedProgress
            }
        }

        deinit {
            observer = nil
        }
    }
}

WebView 上的这个扩展创建了协调器,它继承自 NSObject。它包含对父对象、WebView 和 viewModel 的引用。我们在初始化程序中设置了一个 KVO 观察器来观察 webView 上的 estimatedProgress,这允许我们更新 viewModel 上的进度值。

extension WebView {
    class ProgressViewModel: ObservableObject {
        @Published var progress: Double = 0.0

        init (progress: Double) {
            self.progress = progress
        }
    }
}

最后,这是将 UIViewRepresentableView 联系在一起的 ProgressViewModel

你可以看到它的视频here