使用 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应该是@BindingupdateUIView改变的时候会自动调用。不幸的是,这是 SwiftUI 结构的许多未记录的魔术行为之一。

仅供参考 ObservableObject 旨在将 Combine 管道分配给 @Published。尝试坚持使用数据结构并使用 @State@Binding 使您的值类型具有引用类型语义。