How to inject/evaluate JS in multiple pages of an iOS webview? - ios

Seems like android's WebViewClient has methods like: onProgressChanged()
Anything for ios' wkwebview? (or any other iOS webview)
Webview's onNavigationStateChangedoesn't seem to help here.
Would like to evaluate JS on www.example.com & then onwww.example.com/about.

What exactly do you mean with onNavigationStateChange? This doesn't sound familiar to me.
You should implement WKNavigationDelegate. [https://developer.apple.com/documentation/webkit/wknavigationdelegate].
If you want to load JS after loading an url, you can implement webView(_, didFinish: WKNavigation). WKNavigation offers the api to check request / url.
To load some JS before navigation finished, you can try with webView(WKWebView, decidePolicyFor: WKNavigationAction) [https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455641-webview]
or webView(WKWebView, decidePolicyFor: WKNavigationResponse) [https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview].

See WKUserScript, which is designed specifically for this purpose. When creating the WKWebView, you'll add a WKUserContentController, which can add scripts using addUserScript.

Related

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

When I use WKWebView, I hope that ONLoad can load JS. In the previous UIwebView, this method is possible. Now I must monitor whether the HTML is loaded and then execute the ONLoad parameter. JS cannot be executed synchronously, which is slower than before. A lot.
I control window.scrollTo(x,y) through JS to realize that when he enters the document reading, it will automatically restore to the last reading progress. In the previous UIwebView, he was triggered synchronously.
At present, I use WKWebView, he must first determine the completion of loading HTML, and then trigger this JS, I tried to execute when HTML is loaded, but this is invalid, WKWebView control loading JS has become very slow, I hope it can I found a solution, I hope someone can help me, I don’t know much about English, so this is the result of Google Translate.
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);")
}
})
}

WKWebView Check if web view support to load a file

I am create a iOS Application. I am using WKWebView at some stage to show some files of any extension types. For Audio, Video, html, text, pdf, documents files its working well. But files like .zip , .csv and many others its not showing proper data or show blank screen without any error I am getting in its navigations delegates methods.
My Question is how can I check if my WKWebView is able to load/support that file? So that if it's not able to load or support that file, I can show another view where I will direct download that file to File manager and give user option to save or share that file.
Like In Safari browser when I am trying to load zip file its not show any preview , its show an alert to download (in iOS 13) or show Save and share file action (in iOS 13). How Safari is detecting that its support that file or not?
Please do not suggest to use direct String matching for mime types or extensions because there can be 1000+ files extensions in the world.
You need to make your controller as webView.navigationDelegate and define the navigation response delegate callback as follows:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
if navigationResponse.canShowMIMEType {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
// do something else with `navigationResponse.response`
}
}

How to talk with main thread swift files to access main thread variables

I am a very experienced Android dev but very new to iOS dev using swift. Right now I have setup a UIWebview and a NSURLProtocol using canInitWithRequest (2 separate swift files). The canInitWithRequest method works great but I need to acccess the UIWebview object in order to call functions on the UIWebview object.
For example. if request == google.com then UIWebview.something().
In Android in order to talk with the Main activity thread I just simply create a interface, implement it in my main thread and now I have access to everything on that main activity class.
Thanks for any help.
You may be looking for the method -webView(_:shouldStartLoadWithRequest:navigationType:) in the UIWebViewDelegate protocol.
By implementing this interface, you can have a different object (e.g. the object in your "separate Swift file") determine whether or not to load an NSURLRequest and even do run custom behavior first:
extension MyObject: UIWebViewDelegate {
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request.URL == NSURL(string: "google.com") {
// Do your custom behavior...
webView.something()
}
// Allow the webView to load the request
return true
}
}

UIWebView get request URL path (html string)

I am able to load a HTML string into the UIWebView with CSS obtained from an online source. The problem I have now is to get the path of the link that was clicked. If I use this:
webView.loadHTMLString(finalHtml, baseURL: baseUrl)
I get the path clicked to be "/" when I println(webView.request?.URL.path) in the UIWebViewDelegate shouldStartLoadWithRequest.
If I use this:
webView.loadHTMLString(finalHtml, baseURL: nil)
I get "nil" when I println(webView.request?.URL.path)
Of course, I have set the baseURL to be the original website, but from my understanding, if the link has the full address, the baseURL is irrelevant.
Any pros out there with any advice on how to get the actual path that is indicated in the tags of the link that was clicked? Thank you in advance =D
EDIT When I long click the link, there will be a popup with the correct link shown. I have tried everything, including absoluteString, but I still won't get the path.
If webView.loadHTMLString(finalHTML, baseURL: baseURL) and println(webView.request?.URL.path) are called in the same code block, the webView won't have a non-nil request property yet, and so you can't get the URL path on that request. UIWebView is loading your HTML string asynchronously. You can implement UIWebViewDelegate to see when a link is tapped in your UIWebView. Use the following delegate method:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == .LinkClicked {
println(request.URL.path)
}
return true
}
Leave out the if navigationType == .LinkClicked conditional if you want to see the path of all requests including navigation backwards, forwards, etc.
Have a look at the Documentation for NSURL.
Besides path, there is a property absoluteString that includes the host, base URL etc.
So it seems that it eluded everyone's eyes that I made a silly mistake in my code. Instead of webView.request?.URL.path, I should use request.URL.path. Really small thing that caused me so much agony. So in case anyone is as silly as me, in your shouldStartLoadWithRequest, use request instead of webView.request.

How does phoneGap (Cordova) work internally, iOS specific

I have started developing html applications for mutliple platforms. I recently heard about Cordova 2.0(PhoneGap) and ever since I have been curious to know how the bridge works.
After lot of code walking, i saw that the Exec.js is the code where call from JS -> Native happens
execXhr = execXhr || new XMLHttpRequest();
// Changeing this to a GET will make the XHR reach the URIProtocol on 4.2.
// For some reason it still doesn't work though...
execXhr.open('HEAD', "file:///!gap_exec", true);
execXhr.setRequestHeader('vc', cordova.iOSVCAddr);
if (shouldBundleCommandJson()) {
execXhr.setRequestHeader('cmds', nativecomm());
}
execXhr.send(null);
} else {
execIframe = execIframe || createExecIframe();
execIframe.src = "gap://ready";
But want to understand how that works, what is the concept here, what does file:///!gap_exec or gap://ready do? and how does the call propgate to the lower layers (native code layers)
thanks a bunch in advance.
The trick is easy:
There is a webview. This displays your app. The webview will handle all navigation events.
If the browser navigates to:
file:///!gap_exec
or
gap://
the webview will cancel the navigation. Everything behind these strings is re-used as an identifier, to get the concrete plugin/plugin-method and parameter:
pseudo-url example:
gap://echoplugin/echothistext?Hello World
This will cause phonegap to look for an echoplugin and call the echothistext method to send the text "Hello World" to the (native) plugin.
update
The way back from native to javascript is (or may be) loading a javascript: url into the webview.
The concrete implementation is a little bit more complex, because the javascript has to send a callback-id to native code. There could be more than one native call are running at the same time. But in fact this is no magic at all. Just a number to get the correct JSON to the right javascript-callback.
There are different ways to communicate between the platform and javascript. For Android there are three or four different bridges.
I am trying to figure this out in more detail, too. Basically there are 2 Methods on the iOS side that can help ...
- webView:shouldStartLoadWithRequest:navigationType: and
- stringByEvaluatingJavaScriptFromString:script
From the sources it seems cordova sends a "READY" message using webView:shouldStartLoadWithRequest:... and then picks up results with the second message, but I am not sure.
Cordova Sources iOSExec
There is much to learn there.

Resources