【问题标题】:SwiftUI coordinator not updating the containing view's propertySwiftUI 协调器不更新包含视图的属性
【发布时间】:2020-03-08 09:58:24
【问题描述】:

所以我将WKWebView 包装在UIViewRepresentable 中并构建了一个协调器以访问其导航委托的功能。在webView(_:didFinish:) 函数中,我试图更新视图的didFinishLoading 变量。如果我在分配后立即打印,它会打印 true - 预期的行为。但是,在父视图中,当我调用 getHTML 函数时,它会打印 false - 即使我等到 WKWebView 完全加载。 代码如下:

import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    @Binding var link: String

    init(link: Binding<String>) {
        self._link = link
    }

    private var didFinishLoading: Bool = false

    let webView = WKWebView()

    func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
        self.webView.load(URLRequest(url: URL(string: self.link)!))
        self.webView.navigationDelegate = context.coordinator
        return self.webView
    }

    func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
        return
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        private var webView: WebView

        init(_ webView: WebView) {
            self.webView = webView
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            print("WebView: navigation finished")
            self.webView.didFinishLoading = true
        }
    }

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

    func getHTML(completionHandler: @escaping (Any?) -> ()) {
        print(self.didFinishLoading)
        if (self.didFinishLoading) {
            self.webView.evaluateJavaScript(
                """
                document.documentElement.outerHTML.toString()
                """
            ) { html, error in
                    if error != nil {
                        print("WebView error: \(error!)")
                        completionHandler(nil)
                    } else {
                        completionHandler(html)
                    }
            }
        }
    }
}

struct WebView_Previews: PreviewProvider {
    @State static var link = "https://apple.com"
    static var previews: some View {
        WebView(link: $link)
    }
}

【问题讨论】:

  • 你的 WebView 是结构,所以很有价值,所以它被复制到你的 Coordinator 中。
  • @Asperi 哦,对了......忘了我在一个结构中。有什么办法可以通过引用传递它吗? inout 在这里不适用,因为它会使 makeCoordinator 发生变异,因此它不符合 UIViewRepresentable 协议
  • 一般来说,我倾向于将视图模型推荐为来自 Combine 的 ObservableObject 的子类,并在 SwifthUI 结构之间到处传递它的实例作为参考。
  • @Asperi 但它仍然会使函数可变
  • 在下面的代码中查看我的意思

标签: swift swiftui


【解决方案1】:

这是您的代码,为演示做了一些修改,使用了 ObservableObject 的视图模型实例来保存您的加载状态。

import SwiftUI
import WebKit
import Combine

class WebViewModel: ObservableObject {
    @Published var link: String
    @Published var didFinishLoading: Bool = false

    init (link: String) {
        self.link = link
    }
}

struct WebView: UIViewRepresentable {
    @ObservedObject var viewModel: WebViewModel

    let webView = WKWebView()

    func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
        self.webView.navigationDelegate = context.coordinator
        if let url = URL(string: viewModel.link) {
            self.webView.load(URLRequest(url: url))
        }
        return self.webView
    }

    func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
        return
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        private var viewModel: WebViewModel

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

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            print("WebView: navigation finished")
            self.viewModel.didFinishLoading = true
        }
    }

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

}

struct WebViewContentView: View {
    @ObservedObject var model = WebViewModel(link: "https://apple.com")

    var body: some View {
        VStack {
            TextField("", text: $model.link)
            WebView(viewModel: model)
            if model.didFinishLoading {
                Text("Finished loading")
                    .foregroundColor(Color.red)
            }
        }
    }
}

struct WebView_Previews: PreviewProvider {
    static var previews: some View {
        WebViewContentView()
    }
}

【讨论】:

  • 如果@Binding vars 现在是@Published,我该如何使用?
  • 使用 $ 到 @Published 属性。我用 TextField 更新了示例,使用绑定来链接
  • 谢谢。最后一个问题。有什么方法可以将 @State 与 ViewModel 一起使用?当父级的 link 属性更改时,我希望 WebView 重新呈现
  • @State 被设计为在 View 中使用,所以如果你想将 @State 放入 ViewModel,那么答案是否定的。但是您可以在 View 中拥有一些 @State 并从 ViewModel 为其分配值,这些值可以在类似的视图之间共享。
  • @Asperi,但你可以在 View 中拥有一些 @State 并从 ViewModel 为其分配值 - 不一定。 @State@ObservedObject 工作类似。如果发生突变,它们都会执行 UI 刷新。
猜你喜欢
  • 2020-10-21
  • 1970-01-01
  • 2021-10-19
  • 2019-12-08
  • 2020-07-06
  • 2022-10-21
  • 2020-04-15
  • 1970-01-01
  • 2020-10-21
相关资源
最近更新 更多