WKWebView — оценивайте JavaScript, не видя изменений

В моем приложении SwiftUI я использовал WKWebView для обновления некоторых html-тегов с собственной стороны, используя SwiftUI TextFields. Чтобы связь работала, я использую evaluateJavaScript() метод отправки данных из нативного в веб-просмотр.

Мой код выглядит так:

      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)')")
    }
}

Он работает, внутри didFinish()метод делегата, я устанавливаю начальные значения для отображения на веб-странице. И когда я что-то набираю, оно автоматически обновляется на веб-странице. пока все хорошо, но если я ничего не делаю в течение более длительного времени (около 1 минуты) или если я переключаю приложение (не закрывая его) и возвращаюсь к приложению, а затем что-то меняю в текстовом поле, веб-страница больше не обновляется автоматически.

Я мог бы, конечно, бежать webView.reload(), но тогда я теряю свое предыдущее состояние. Вместо InitialЯ получаю заголовок. Логично, потому что это было установлено внутри тега h3, но этого я хочу избежать.

Итак, как я могу убедиться, что всякий раз, когда evaluteJavascript()вызывается, веб-страница также обновляется без потери предыдущего состояния, независимо от того, как долго я жду или переключаюсь между приложениями? Есть ли способ решить эту проблему?

1 ответ

Это выглядит как дефект Apple, потому что вызывается updateUIView, но evaluateJavaScript(внутренний JavaScript) генерирует исключение, потому что bridge.onMessage потерян, а не функция больше.

Здесь найден обходной путь - сбросить WebView при активации сцены (приложение выходит на передний план), так как оно воссоздано с текущими состояниями, предыдущие значения сохраняются. Протестировано с 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 !!
                        }
                    }
            }
        }
    }
}
Другие вопросы по тегам