En mi aplicación SwiftUI, usé WKWebView para actualizar algunas etiquetas html desde el lado nativo, usando SwiftUI TextFields. Para que la comunicación funcione, estoy usando el método de evaluación de JavaScript evaluateJavaScript() para enviar datos de nativo a webview.
Mi código se ve así:
struct DemoView: View { @State private var headline: String = "Initial" var body: some View { NavigationView { VStack { Form { TextField("Your headline", text: $headline) } WebView(headline: $headline) } } } } import WebKit let bridgeHTML = """ <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, minimum-scale=1, viewport-fit=cover"> </head> <body> <h3 id="headline">Headline</h3> <script> // to receive messages from native webkit.messageHandlers.bridge.onMessage = (msg) => { document.getElementById("headline").textContent = msg } </script> </body> </html> """ struct WebView: UIViewRepresentable { @Binding var headline: String class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { } private var owner: WebView init(owner: WebView) { self.owner = owner } var webView: WKWebView? func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.webView = webView self.messageToWebview(msg: self.owner.headline) // initial value loading !! } func messageToWebview(msg: String) { self.webView?.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage('\(msg)')") } } func makeCoordinator() -> Coordinator { return Coordinator(owner: self) } func makeUIView(context: Context) -> WKWebView { let userContentController = WKUserContentController() userContentController.add(context.coordinator, name: "bridge") let configuration = WKWebViewConfiguration() configuration.userContentController = userContentController let _wkwebview = WKWebView(frame: .zero, configuration: configuration) _wkwebview.navigationDelegate = context.coordinator guard let path: String = Bundle.main.path(forResource: "index", ofType: "html") else { return _wkwebview } let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false) _wkwebview.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl) // _wkwebview.loadHTMLString(bridgeHTML, baseURL: nil) // << used for testing return _wkwebview } func updateUIView(_ webView: WKWebView, context: Context) { // this works for update, but for initial it is too early !! webView.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage('\(headline)')") } } Funciona, dentro del método de delegado didFinish() configuro los valores iniciales para representar en la página web. Y cuando escribo algo, se actualiza automáticamente en la página web. hasta ahora todo bien, pero si no hago nada durante más tiempo (alrededor de 1 minuto) o si cambio la aplicación (no la cierro) y vuelvo a la aplicación y luego cambio algo en el campo de texto, la página web es ya no se actualiza automáticamente.
Por supuesto, podría ejecutar webView.reload() , pero luego pierdo mi estado anterior. En lugar de Initial , obtengo Titular. Lógico porque esto se configuró dentro de la etiqueta h3, pero eso es lo que quiero evitar.
Entonces, ¿cómo puedo asegurarme de que siempre que se evaluteJavascript() , la página web también se actualice sin perder el estado anterior, sin importar cuánto tiempo espere o si cambio de aplicación? ¿Hay alguna forma de resolver esto?
Esto parece un defecto de Apple, porque se llama a updateUIView, pero evaluateJavaScript (JavaScript interno) genera una excepción, porque se pierde bridge.onMessage y no una función más.
Aquí se encuentra una solución alternativa: restablecer WebView en la activación de la escena (la aplicación pasa a primer plano), ya que se recreó con los estados actuales, se conservan los valores anteriores. Probado con Xcode 13.2 / iOS 15.2
struct DemoView: View { @Environment(\.scenePhase) var scenePhase @State private var headline: String = "Initial" @State private var reset = false var body: some View { NavigationView { VStack { Form { TextField("Your headline", text: $headline) } WebView(headline: $headline).id(reset) // << here !! .onChange(of: scenePhase) { if case .active = $0 { self.reset.toggle() // << here !! } } } } } }