3

I want javascript to send a message back to my WKWebView object, but I get nothing in response. I know the script is running, as the color changes, but I am expecting to also see "trigger from JS" printed in the console, which I don't. If I run the html in Chrome, the javascript console says "Cannot read property 'messageHandlers' of undefined". If I build for iOS (using UIViewRepresentable, MakeUIView and UpdateUIView) the result is the same. If anyone can spot what I have missed I would greatly appreciate it.

This is the entirety of the code:

import SwiftUI
import WebKit

class HtmlData {
    let html = """
    <!DOCTYPE html>
    <html>
    <body>

    <button onclick="sendMessage()">Send Message</button>
    <script>
    function sendMessage() {
      document.body.style.backgroundColor = "red";
     window.webkit.messageHandlers.testMessage.postMessage("trigger from JS");
    }
    </script>
    </body>
    </html>
    """
}

struct ContentView: View {
    let htmlData = HtmlData()
    var body: some View {
        JSWebView(html: htmlData.html)
    }
}

struct JSWebView: NSViewRepresentable {
    let html: String
    func makeNSView(context: Context) -> WKWebView {
        let preferences = WKPreferences()
        preferences.javaScriptEnabled = true
        return WKWebView()
    }
    
    func updateNSView(_ view: WKWebView, context: Context) {
        let userContentController = WKUserContentController()
        let handler = ContentController(view)
        let configuration = WKWebViewConfiguration()
        configuration.userContentController = userContentController
        configuration.userContentController.add(handler, name: "testMessage")
        view.loadHTMLString(html, baseURL: Bundle.main.bundleURL)
    }
    
    class ContentController: NSObject, WKScriptMessageHandler {
        var parent: WKWebView?
        init(_ parent: WKWebView?) {
            self.parent = parent
        }
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            print(message.body)
        }
    }
}

1 Answer 1

3

Configuration must be passed into the constructor of WKWebView. It can't be set after initialization.

struct JSWebView: NSViewRepresentable {
    let html: String
    
    func makeNSView(context: Context) -> WKWebView {
        let preferences = WKPreferences()
        preferences.javaScriptEnabled = true
        let handler = MessageHandler()
        let configuration = WKWebViewConfiguration()
        configuration.userContentController.add(handler, name: "testMessage")
        return WKWebView(frame: .zero, configuration: configuration)
    }
    
    func updateNSView(_ view: WKWebView, context: Context) {
        view.loadHTMLString(html, baseURL: Bundle.main.bundleURL)
    }
    
    class MessageHandler: NSObject, WKScriptMessageHandler {
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            print(message.body)
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Bloody brilliant. I have no where near the chops I needed to solve that, and no amount of scouring google would do it. My hats off you sir. Thank you!
Do I mark the question as answered somewhere? I did give it a vote, but of course it doesn't show as I am too new.
Click the check mark under the vote counter in the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.