使用 Wkwebview 更改 ObservedObject 值
Change ObservedObject Value with Wkwebview
我正在为我的应用程序使用 WkWebView
。加载视图后,我想本地显示分数而不是 WkWebView
。我使用 combine
框架工作创建了一个 ObservableObject
,以便在 WkWebview
中分数发生变化时向视图和其他视图显示我的分数。我使用 window.onload
获取最新分数并在页面首次呈现时显示它。我通过调用一个 JS 函数来实现这一点,该函数将带有分数的消息发送到本机端 webkit.messageHandlers.bridge.postMessage("0")
,并将发送的分数分配给我的 ObservedObject
。问题出在本机方面。 UserContentController
函数处理来自 WkWebview
的消息,不断打印分数并将分数重新分配给我的 ObservedObject
。它似乎陷入了一个循环。我提供了以下代码的简化版本。已经坚持了几天,似乎无法解决问题。
//Holds the score
class Myscore:ObservableObject{
@Published var score = "0"
}
//Wkwebview
struct WebView: UIViewRepresentable {
@ObservedObject var myScore : Myscore
class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView?
var myScore: Myscore
init(myScore:Myscore) {
self.myScore = myScore
super.init()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webView = webView
}
// receive message from wkwebview
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
// This is where the issue occurs
var newscore = message.body as! String
myScore.score = newscore
print(myScore.score)
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(myScore:myScore)
}
func makeUIView(context: Context) -> WKWebView {
let coordinator = makeCoordinator()
let userContentController = WKUserContentController()
userContentController.add(coordinator, name: "bridge")
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
let _wkwebview = WKWebView(frame: .zero, configuration: configuration)
_wkwebview.navigationDelegate = coordinator
return _wkwebview
}
func updateUIView(_ webView: WKWebView, context: Context) {
guard let path: String = Bundle.main.path(forResource: "index", ofType: "html")
else { return }
let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
webView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
}
}
//Content View to display the Score
struct ContentView: View {
@StateObject var myScore = Myscore()
var body: some View {
VStack {
Text("Your Score is\( myScore.score)")
WebView(myScore: myScore)
}
}
}
编辑这里是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>
<button>click me</button>
<hr/>
<div id="log"></div>
<script>
window.onload = function () {
webkit.messageHandlers.bridge.postMessage("98 points")
}
</script>
</body>
</html>
编辑 2:这是控制台打印的内容
在这里我看到通过更新 Myscore
导致 updateUIView
调用等的循环加载...
初始加载应放入makeUIView
func makeUIView(context: Context) -> WKWebView {
let coordinator = makeCoordinator()
let userContentController = WKUserContentController()
userContentController.add(coordinator, name: "bridge")
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
let _wkwebview = WKWebView(frame: .zero, configuration: configuration)
_wkwebview.navigationDelegate = coordinator
// make here initial loading !!!
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) {
// reload should be made here only if base url changed
// externally
}
测试 Xcode 13.4 / iOS 15.5
score
应该是@Binding
,updateUIView
改变的时候会自动调用。不幸的是,这是 SwiftUI 结构的许多未记录的魔术行为之一。
仅供参考 ObservableObject
旨在将 Combine 管道分配给 @Published
。尝试坚持使用数据结构并使用 @State
和 @Binding
使您的值类型具有引用类型语义。
我正在为我的应用程序使用 WkWebView
。加载视图后,我想本地显示分数而不是 WkWebView
。我使用 combine
框架工作创建了一个 ObservableObject
,以便在 WkWebview
中分数发生变化时向视图和其他视图显示我的分数。我使用 window.onload
获取最新分数并在页面首次呈现时显示它。我通过调用一个 JS 函数来实现这一点,该函数将带有分数的消息发送到本机端 webkit.messageHandlers.bridge.postMessage("0")
,并将发送的分数分配给我的 ObservedObject
。问题出在本机方面。 UserContentController
函数处理来自 WkWebview
的消息,不断打印分数并将分数重新分配给我的 ObservedObject
。它似乎陷入了一个循环。我提供了以下代码的简化版本。已经坚持了几天,似乎无法解决问题。
//Holds the score
class Myscore:ObservableObject{
@Published var score = "0"
}
//Wkwebview
struct WebView: UIViewRepresentable {
@ObservedObject var myScore : Myscore
class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView?
var myScore: Myscore
init(myScore:Myscore) {
self.myScore = myScore
super.init()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webView = webView
}
// receive message from wkwebview
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
// This is where the issue occurs
var newscore = message.body as! String
myScore.score = newscore
print(myScore.score)
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(myScore:myScore)
}
func makeUIView(context: Context) -> WKWebView {
let coordinator = makeCoordinator()
let userContentController = WKUserContentController()
userContentController.add(coordinator, name: "bridge")
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
let _wkwebview = WKWebView(frame: .zero, configuration: configuration)
_wkwebview.navigationDelegate = coordinator
return _wkwebview
}
func updateUIView(_ webView: WKWebView, context: Context) {
guard let path: String = Bundle.main.path(forResource: "index", ofType: "html")
else { return }
let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
webView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
}
}
//Content View to display the Score
struct ContentView: View {
@StateObject var myScore = Myscore()
var body: some View {
VStack {
Text("Your Score is\( myScore.score)")
WebView(myScore: myScore)
}
}
}
编辑这里是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>
<button>click me</button>
<hr/>
<div id="log"></div>
<script>
window.onload = function () {
webkit.messageHandlers.bridge.postMessage("98 points")
}
</script>
</body>
</html>
编辑 2:这是控制台打印的内容
在这里我看到通过更新 Myscore
导致 updateUIView
调用等的循环加载...
初始加载应放入makeUIView
func makeUIView(context: Context) -> WKWebView {
let coordinator = makeCoordinator()
let userContentController = WKUserContentController()
userContentController.add(coordinator, name: "bridge")
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
let _wkwebview = WKWebView(frame: .zero, configuration: configuration)
_wkwebview.navigationDelegate = coordinator
// make here initial loading !!!
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) {
// reload should be made here only if base url changed
// externally
}
测试 Xcode 13.4 / iOS 15.5
score
应该是@Binding
,updateUIView
改变的时候会自动调用。不幸的是,这是 SwiftUI 结构的许多未记录的魔术行为之一。
仅供参考 ObservableObject
旨在将 Combine 管道分配给 @Published
。尝试坚持使用数据结构并使用 @State
和 @Binding
使您的值类型具有引用类型语义。