WKWebView - 评估 JavaScript 没有看到变化
WKWebView - evaluate JavaScript without seeing changes
在我的 SwiftUI 应用程序中,我使用 WKWebView 使用 SwiftUI TextFields 从本机端更新一些 html 标签。为了使通信正常工作,我使用 evaluateJavaScript()
方法将数据从本机发送到 webview。
我的代码如下所示:
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()
时,网页也会更新而不会丢失之前的状态?有什么办法可以解决吗?
这看起来像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 = [=10=] {
self.reset.toggle() // << here !!
}
}
}
}
}
}
在我的 SwiftUI 应用程序中,我使用 WKWebView 使用 SwiftUI TextFields 从本机端更新一些 html 标签。为了使通信正常工作,我使用 evaluateJavaScript()
方法将数据从本机发送到 webview。
我的代码如下所示:
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()
时,网页也会更新而不会丢失之前的状态?有什么办法可以解决吗?
这看起来像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 = [=10=] {
self.reset.toggle() // << here !!
}
}
}
}
}
}