3

How to get evaluateJavaScript to work when a message is received from webview?

print(message.body) <--- this is working

parent?.evaluateJavaScript("document.getElementsByClassName('mat-toolbar-single-row')[0].style.backgroundColor = 'red'", completionHandler: nil) <--- this is not

struct WebView: UIViewRepresentable {

let request: URLRequest

let contentController = ContentController(nil)


func makeUIView(context: Context) -> WKWebView {
    let webConfiguration = WKWebViewConfiguration()
    let wkcontentController = WKUserContentController()
    wkcontentController.add(contentController, name: "test")
    webConfiguration.userContentController = wkcontentController
    return WKWebView(frame: .zero, configuration: webConfiguration)
}



func updateUIView(_ view: WKWebView, context: Context) {        
    view.load(request)
}

class ContentController: NSObject, WKScriptMessageHandler {
    var parent: WKWebView?
    init(_ parent: WKWebView?) {
        self.parent = parent
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)  {
        if message.name == "test"{
            print(message.body)
            parent?.evaluateJavaScript("document.getElementsByClassName('mat-toolbar-single-row')[0].style.backgroundColor = 'red'", completionHandler: nil)

        }
    }
}
2
  • This topic about WebView should be helpful as besides other it uses script evaluation. Commented Feb 10, 2020 at 5:44
  • I tried this example before posting my question, and I got ` Instance member "webView" of type '"WebView" cannot be used on instance of nested type "WebView.ContentController" ` Commented Feb 10, 2020 at 6:31

2 Answers 2

2

Here is possible approach. As I don't have intended testing environment I could not test it completely, but all infrastructure constructed correctly. So you can try

struct WebView: UIViewRepresentable {

    let request: URLRequest

    func makeUIView(context: Context) -> WKWebView {
        let webConfiguration = WKWebViewConfiguration()
        let wkcontentController = WKUserContentController()

        wkcontentController.add(context.coordinator, name: "test")
        webConfiguration.userContentController = wkcontentController

        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        context.coordinator.parent = webView // inject as weak

        return webView
    }

    func updateUIView(_ view: WKWebView, context: Context) {
        if view.url == nil {
            view.load(request)
        }
    }

    func makeCoordinator() -> ContentController {
        ContentController() // let handler be a coordinator
    }

    class ContentController: NSObject, WKScriptMessageHandler {
        weak var parent: WKWebView? // weak to avoid reference cycling

        func userContentController(_ userContentController: WKUserContentController, 
                                   didReceive message: WKScriptMessage)  {
            if message.name == "test" {
                print(message.body)
                parent?.evaluateJavaScript("document.getElementsByClassName('mat-toolbar-single-row')[0].style.backgroundColor = 'red'", 
                    completionHandler: nil)

            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

I tested your code and it works, now both answers work I don't know if one is best practice.
1

The answer is to pass WKWebview to ContentController() instead of nil

Here is a working example;

struct WebView: UIViewRepresentable {

let request: URLRequest

func makeUIView(context: Context) -> WKWebView {
            return WKWebView()
}



func updateUIView(_ view: WKWebView, context: Context) {
    let contentController = ContentController(view)
    let userScript = WKUserScript(source: "document.getElementsByClassName('mat-toolbar-single-row')[0].style.backgroundColor = 'black'; alert('from iOS')", injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: true)
    view.configuration.userContentController.add(contentController, name: "test")
    view.configuration.userContentController.addUserScript(userScript)
    view.evaluateJavaScript("document.body.style.backgroundColor = '#4287f5';", completionHandler: nil)
    view.load(request)
}

class ContentController: NSObject, WKScriptMessageHandler {

    var parent: WKWebView?
    init(_ parent: WKWebView?) {
        self.parent = parent
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)  {
        parent?.evaluateJavaScript("document.getElementsByClassName('mat-toolbar-single-row')[0].style.backgroundColor = 'red';", completionHandler: nil)
            print("from test")
    }
}
}

Comments

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.