JavaScript in Wkwebview - Evaluatejavascript VS Adduserscript

WKWebView Evaluate Javascript without reloading page

[webView evaluateJavaScript:javascriptString completionHandler:nil];

performs the same function as the one you have listed for UIWebView

evaluateJavaScript() in WKWebView works only the first time

Updating the value of var in javascript is rather dangerous in my opinion. The script could have been already executed at the time, not even mentioning other Javascript quirks with var. Ideally, you should pass the values using a javascript function that would take the new values as parameters, for example:

function update(oneWeek, oneMonth) { // add other parameters
document.querySelector('.oneWeek').innerHTML = oneWeek;
}

and then:

webView.evaluateJavaScript("update(\"\(oneWeek)\", \"\(oneMonth)\");") { (result, error) in

Another part of the problem is the fact that loadHTMLString starts the loading but we don't know whether the content has already been loaded or not when calling evaluateJavaScript. Ideally, you should call the update in one of the WKNavigationDelegate methods.

As an alternative approach, if you have the HTML, you could directly replace the values there before loading, e.g. putting some replacement templates there (e.g. $ONE_WEEK). That would be probably much easier to handle.

How to inject multiple user scripts in WKWebView to obtain darkmode effect?

Using this tutorial I implemented the effect with mix-blend-mode: difference.

Inject multiple WKUserScript in the webiew at document start and document end:

  1. JavaScript file with toggle function at document start
  2. Inject div container with background and blender that will make the blend difference (at document end)
  3. Inside style tags contents of CSS file at document end

    let webConfiguration = WKWebViewConfiguration()
    let contentController = WKUserContentController()

    // Libray script an document start
    let darkModeScript = WKUserScript(source: self.darkModeLibraryJS, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: false)
    contentController.addUserScript(darkModeScript)

    let injectDarkModeScript = WKUserScript(source: self.injectDarkModeJS, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: false)
    contentController.addUserScript(injectDarkModeScript)

    let injectCSScript = WKUserScript(source: self.injectCSS, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: false)
    contentController.addUserScript(injectCSScript)

    webConfiguration.userContentController = contentController

    self.webview = WKWebView(frame: CGRect.zero, configuration: webConfiguration)
    self.webview?.navigationDelegate = self
    self.view.addSubview(webview!)

    self.webview!.loadHTMLString(html, baseURL: nil)

JavaScript with toggle function and inject CSS (darkmode.js)

function injectCSS(css) {
head = document.head || document.getElementsByTagName('head')[0],
style = document.createElement('style');
head.appendChild(style);

style.type = 'text/css';
if (style.styleSheet){
// This is required for IE8 and below.
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}

function showDarkMode() {
var blender = document.getElementById('blender')
if (blender.hasAttribute("hidden")) {
blender.removeAttribute("hidden")
}
}

function showOriginalMode() {
var blender = document.getElementById('blender')

if (!blender.hasAttribute("hidden")) {
blender.setAttribute("hidden", true)
}
}

JavaScript with adding div container to DOM of the webview (inject-darkmode.js)

var container = document.createElement('div')
container.id = 'darkmode-container'
document.body.appendChild(container)

var background = document.createElement('div')
background.classList.add('darkmode-background')
container.appendChild(background)

var blender = document.createElement('div')
blender.id = 'blender'
blender.setAttribute('hidden', true)
container.appendChild(blender)

CSS for defining blender and background (darkmode.css)

#blender {
width: 100vw;
height: 100vh;
left: 0pt;
top: 0pt;
position: fixed;
background: white;
transition: all 1s ease;
mix-blend-mode: difference;
}

img {
isolation: isolate;
}

.darkmode-background {
position: fixed;
background: white;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: -1;
}

Repository with working project: https://github.com/CristiGhn/darkmode-webview

Call JavaScript function from native code in WKWebView

(I filed a Radar for this shortly after asking the question here.)

A new method was just added a few days ago (thanks jcesarmobile for pointing it out):

Add -[WKWebView evaluateJavaScript:completionHandler:]
http://trac.webkit.org/changeset/169765

The method is available in iOS 8 beta 3 and up. Here's the new method signature:

/* @abstract Evaluates the given JavaScript string. 
@param javaScriptString The JavaScript string to evaluate.
@param completionHandler A block to invoke when script evaluation completes
or fails.
@discussion The completionHandler is passed the result of the script evaluation
or an error.
*/
- (void)evaluateJavaScript:(NSString *)javaScriptString
completionHandler:(void (^)(id, NSError *))completionHandler;

Docs are available here: https://developer.apple.com/documentation/webkit/wkwebview/1415017-evaluatejavascript.

evaluateJavaScript in swiftui

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)

}
}
}
}

How WKWebView runs JS to control window.scrollTo(x,y) when loading document

You can inject a script that should be executed on load like this:

NSString *scriptString = @"window.scrollTo(0,1000);";
WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[self.webView.configuration.userContentController addUserScript:script];

Here you can find the documentation of WKUserScript: https://developer.apple.com/documentation/webkit/wkuserscript?language=objc

If the injection time is still too early you can try to add a WKNavigationDelegate and call your script like below (sorry for the Swift code, maybe someone can edit and convert to objective-c)

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
self.webView.evaluateJavaScript("window.scrollTo(0,1000);")
}
})
}


Related Topics



Leave a reply



Submit