WKWebView - 从 SwiftUI TextFields 更新 HTML 标签
WKWebView - Update HTML Tags from SwiftUI TextFields
在我的 SwiftUI 应用程序中,我使用 WKWebView 从本机端更新一些 html 标签,使用 SwiftUI TextFields 和 TextEditor。为了使通信正常工作,我使用 webkit.messageHandlers.bridge.onMessage()
将数据从本机发送到 webview。有时有效,有时无效。
我的 WebView
看起来像这样:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
@Binding var headline: String
@Binding var subheadline: String
@Binding var description: String
class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView?
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webView = webView
}
func messageToWebview(msg: String) {
self.webView?.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage('\(msg)')")
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
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)
return _wkwebview
}
func updateUIView(_ webView: WKWebView, context: Context) {
webView.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage({ type: 'headline', content: '\(headline)' })")
webView.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage({ type: 'subheadline', content: '\(subheadline)' })")
webView.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage({ type: 'description', content: '\(description)' })")
}
}
我的 index.html
代码如下所示:
<!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>
<p id="subheadline">Subheadline</p>
<p id="description">Description</p>
<script>
// to receive messages from native
webkit.messageHandlers.bridge.onMessage = (data) => {
if (data.type === 'headline') {
document.getElementById("headline").textContent = data.content
}
if (data.type === 'subheadline') {
document.getElementById("subheadline").textContent = data.content
}
if (data.type === 'description') {
document.getElementById("description").textContent = data.content
}
}
</script>
</body>
</html>
我在 ContentView
中使用所有这些,如下所示:
import SwiftUI
struct ContentView: View {
@State private var headline: String = ""
@State private var subheadline: String = ""
@State private var description: String = ""
var body: some View {
NavigationView {
Form {
TextField("Your headline", text: $headline)
TextField("Subheadline", text: $subheadline)
TextEditor(text: $description)
}
WebView(
headline: $headline,
subheadline: $subheadline,
description: $description
)
}
}
}
如您所见,我正在使用绑定实时更新 headline
、subheadline
和我的 description
。因此,例如,当我在标题文本字段中键入时,标题将被更新等。
问题是这并不总是有效。例如,如果我打开应用程序并首先显示和隐藏侧边栏,然后在标题文本字段中写一些东西,h3
不会更新。或者当我在标题和副标题文本字段中键入内容然后我想编辑描述时,描述将不会更新。它停止工作,为什么?
我的错误在哪里,我该如何解决?有什么想法吗?
这是适用于 Xcode 13.2 / iOS 15.2 的固定变体的完整模块。固定协调器,当然你应该始终从上下文中使用它,以及实体之间的多次注入和通信。
注意:另请参阅内联评论
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)')")
}
}
在我的 SwiftUI 应用程序中,我使用 WKWebView 从本机端更新一些 html 标签,使用 SwiftUI TextFields 和 TextEditor。为了使通信正常工作,我使用 webkit.messageHandlers.bridge.onMessage()
将数据从本机发送到 webview。有时有效,有时无效。
我的 WebView
看起来像这样:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
@Binding var headline: String
@Binding var subheadline: String
@Binding var description: String
class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView?
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webView = webView
}
func messageToWebview(msg: String) {
self.webView?.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage('\(msg)')")
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
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)
return _wkwebview
}
func updateUIView(_ webView: WKWebView, context: Context) {
webView.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage({ type: 'headline', content: '\(headline)' })")
webView.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage({ type: 'subheadline', content: '\(subheadline)' })")
webView.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage({ type: 'description', content: '\(description)' })")
}
}
我的 index.html
代码如下所示:
<!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>
<p id="subheadline">Subheadline</p>
<p id="description">Description</p>
<script>
// to receive messages from native
webkit.messageHandlers.bridge.onMessage = (data) => {
if (data.type === 'headline') {
document.getElementById("headline").textContent = data.content
}
if (data.type === 'subheadline') {
document.getElementById("subheadline").textContent = data.content
}
if (data.type === 'description') {
document.getElementById("description").textContent = data.content
}
}
</script>
</body>
</html>
我在 ContentView
中使用所有这些,如下所示:
import SwiftUI
struct ContentView: View {
@State private var headline: String = ""
@State private var subheadline: String = ""
@State private var description: String = ""
var body: some View {
NavigationView {
Form {
TextField("Your headline", text: $headline)
TextField("Subheadline", text: $subheadline)
TextEditor(text: $description)
}
WebView(
headline: $headline,
subheadline: $subheadline,
description: $description
)
}
}
}
如您所见,我正在使用绑定实时更新 headline
、subheadline
和我的 description
。因此,例如,当我在标题文本字段中键入时,标题将被更新等。
问题是这并不总是有效。例如,如果我打开应用程序并首先显示和隐藏侧边栏,然后在标题文本字段中写一些东西,h3
不会更新。或者当我在标题和副标题文本字段中键入内容然后我想编辑描述时,描述将不会更新。它停止工作,为什么?
我的错误在哪里,我该如何解决?有什么想法吗?
这是适用于 Xcode 13.2 / iOS 15.2 的固定变体的完整模块。固定协调器,当然你应该始终从上下文中使用它,以及实体之间的多次注入和通信。
注意:另请参阅内联评论
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)')")
}
}