1

I am trying to run a javascript command that works if I run it in the console on the web. I have tried doing the code below, but I keep getting nil as my response.

webView.evaluateJavaScript("command") { (result, error) in
     if error == nil {
          print(result)
     } else {
          print(error)
     }
}
Ze_Reid
  • 21
  • 1
  • 6
  • For correction ```if error != nil { print(result) }``` it means the is an error ---> There is no result then. – Canh Tran Aug 10 '20 at 02:51
  • I fixed it and this is the error that I get: `Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=TypeError: undefined is not an object, WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=A JavaScript exception occurred` I just want to run a command that I know for sure works when I put it in the javascript console in chrome on my computer. Is this possible? – Ze_Reid Aug 10 '20 at 03:00
  • Test your command directly in WKWebView. You can attach a desktop Safari’s web inspector to the web view that is currently opened in the iOS simulator. – Eugene Dudnyk Aug 10 '20 at 06:32
  • A sample javascript command you could try is `(() => { return 5 })();` and see if you get a 5 for result on iOS. The javascript just creates and runs a function that returns the value 5. – rohanphadte Aug 20 '20 at 03:07

2 Answers2

1

In case if your webview loads a part of body(or whole html body) additionally, then you won't have desired results by evaluating your javascript inside of method:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) 

Reason for this is because "didFinish" method is called once navigation is done inside of webview, but not strictly after loading whole content inside of webView which could be in states:

  1. state: empty body
  2. state: some loading
  3. state: javascript framework did his job and inserted app-body inside
  4. state: body has content

"didFinish" is called in state 1. You can wait for some time with DispatchQueue.main.asyncAfter and then do the .evaluateJavascript but it will make webView makes changes in front of user(ugly per my opinion...)

My proposal would be this extension:

extension WKWebView {

// it re-execute javascript in cases when body is attached additionally on the html by some inner javascript framework
// it tries around 50 times maximum in time range of 10 sec
// if body is not loaded in 10 secs, this won't work
func rexecuteJavascript(tryouts: Int = 0, script: String) {
    self.evaluateJavaScript(script, completionHandler: { (result: Any?, error: Error?) in
        if let _ = error, tryouts < 50 {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                return self.rexecuteJavascript(tryouts: tryouts + 1, script: script)
            }
        }
    })
}

}

It works perfectly for me because body of webview gets populated in around 2-3 secs after "didFinish", and I use this to execute my javascript on fully loaded body:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    let removeFooterScript = "document.getElementsByClassName('global-footer')[0].style.display='none';"

    webView.rexecuteJavascript(script: removeFooterScript)
}

Cheers!

Elvis Rudonja
  • 389
  • 3
  • 5
0

Here is the code to inject javascript code in your WKWebView

let wkUScript = WKUserScript(source: "your javascript command",
                             injectionTime: .atDocumentEnd,
                             forMainFrameOnly: true)
let wkUController = WKUserContentController()
wkUController.addUserScript(wkUScript)

let wkWebConfig = WKWebViewConfiguration()
wkWebConfig.userContentController = wkUController

let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 400, height: 400), configuration: wkWebConfig)
The Mach System
  • 6,703
  • 3
  • 16
  • 20
  • How would you print out the response? – Ze_Reid Aug 10 '20 at 16:26
  • You set `webView.UIDelegate = self` then call this delegate: `func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { print(message) }` – The Mach System Aug 11 '20 at 05:09