如何在 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) 跟踪 WKWebView
的 estimatedProgress。为此,我们需要向 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 一起传递给 WebView
。 ProgressView
从 ProgressViewModel
传递 @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
没有太大区别,只是我们要传递 URL
和 ProgressViewModel
。我们还从 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
}
}
}
最后,这是将 UIViewRepresentable
和 View
联系在一起的 ProgressViewModel
。
你可以看到它的视频here
我尝试就此事搜索数周,但找不到在 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) 跟踪 WKWebView
的 estimatedProgress。为此,我们需要向 WebView
.
仅仅添加协调器是不够的,我们需要一种方法将值从 UIViewRepresentable
传回 View
。我们可以使用 @Binding
或闭包来完成此操作,但这会在更新视图时导致以下错误:
Warning: Modifying state during view update, this will cause undefined behavior.
一个“hacky”解决方案是使用 DispatchQueue.main.async
包装绑定 progress
变量的设置,但这不是一个好的解决方案。
解决此问题的更好方法是创建一个符合 ObservableObject
.
这给出了以下代码:
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 一起传递给 WebView
。 ProgressView
从 ProgressViewModel
传递 @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
没有太大区别,只是我们要传递 URL
和 ProgressViewModel
。我们还从 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
}
}
}
最后,这是将 UIViewRepresentable
和 View
联系在一起的 ProgressViewModel
。
你可以看到它的视频here