In my SwiftUI App, i used a WKWebView to update some html tags from native side, using SwiftUI TextFields. To get the communication working, i am using the evaluateJavaScript()
method to send data from native to webview.
My code looks like this:
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)')")
}
}
It works, inside the didFinish()
delegate method i set the initial values to render on the webpage. And when i type something, it automatically updates on the webpage. so far so good, but if I don't do anything for a longer time (about 1 minute) or if I switch the app (not close it) and go back to the app and then change anything in the textfield, the webpage is not updated automatically anymore.
I could of course run webView.reload()
, but then I lose my previous state. Instead of Initial
I get Headline. Logical because this was set inside the h3 tag, but that's what i want to avoid.
So how can i make sure that whenever evaluteJavascript()
is called, the webpage is also updated without losing the previous state, no matter how long i wait or if i switch between apps? Is there any way to solve this?