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:
- JavaScript file with toggle function at document start
- Inject div container with background and blender that will make the blend difference (at document end)
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
Mediastream Capture Canvas and Audio Simultaneously
Dt: Link Binding Is Lost After Re-Rendering the Table
Can Jquery .Keypress() Detect More Than One Key at the Same Time
Convert Array of Strings into an Array of Objects
Javascript:Send JSON Object with Ajax
Conditionalpanel JavaScript Conditions in Shiny: Is There R %In% Operator in JavaScript
Differencebetween Compile and Link Function in Angularjs
Simplest Way to Wait Some Asynchronous Tasks Complete, in JavaScript
Angular Cli Webpack, How to Add or Bundle External Js Files
How to Control the Back Button Event in Jquery Mobile
R Shiny Build Links Between Tabs
Escape String - Output Rails String in JavaScript
How Persistent Is Localstorage
Console.Log() After Setstate() Doesn't Return the Updated State
Set a Request Header in JavaScript
Shiny: Passing Reactivevalues to Conditionalpanel