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)

        }
    }
}
faris97
  • 402
  • 4
  • 24
Nouras
  • 129
  • 2
  • 12
  • [This topic about WebView](https://stackoverflow.com/a/59790493/12299030) should be helpful as besides other it uses script evaluation. – Asperi Feb 10 '20 at 05:44
  • I tried [this](https://stackoverflow.com/questions/59789950/swiftui-wkwebview-content-height-issue/59790493#59790493) example before posting my question, and I got ` Instance member "webView" of type '"WebView" cannot be used on instance of nested type "WebView.ContentController" ` – Nouras Feb 10 '20 at 06:31

2 Answers2

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)

            }
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • I tested your code and it works, now both answers work I don't know if one is best practice. – Nouras Feb 11 '20 at 18:32
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")
    }
}
}
Nouras
  • 129
  • 2
  • 12