evaluateJavascript is not executing function - ios

How do i execute javascript function at runtime, the function to load the chat window does not get executed
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let javascript =
"const params = {typeId: ‘someid’, callback: getContextCallback} loadChatWindow(params)"
evaluateJavascript(javascript, completion:{ _ in })
}

try catching your error in evaluateJavascript completionHandler to see if your javascript string is correct or not (you need semicolon to separate the js statements as mentioned in the comment). also, evaluateJavascript is webView's method so it should be called like this:
webView.evaluateJavaScript(javascript) { (result, error) in
print(error as? String)}

Related

Write Unit Tests to validate WKWebView load request

I have a WKWebView to loads a basic url request.
extension ViewController: WKUIDelegate, WKScriptMessageHandler, WKNavigationDelegate {
func webView(
_ webView: WKWebView,
createWebViewWith configuration: WKWebViewConfiguration,
for navigationAction: WKNavigationAction,
windowFeatures: WKWindowFeatures
) -> WKWebView? {
if navigationAction.targetFrame == nil, let url = navigationAction.request.url {
if url.description.lowercased().range(of: "http://") != nil ||
url.description.lowercased().range(of: "https://") != nil ||
url.description.lowercased().range(of: "mailto:") != nil {
UIApplication.shared.open(url)
}
}
return nil
}
func openSite() {
guard let myUrl = URL(string: "https://www.myurl.com") else { return }
let request = URLRequest(url: myUrl)
webView?.load(request)
self.webView.sizeToFit()
}
Now I want to write a unit test to verify webview correctly load the request. I have followed this approach https://stackoverflow.com/a/63827560/627667 and created a mock navigation action.
func test_AllowsCorrectURL() {
let action = MockNavigationAction()
action.mockedRequest = URLRequest(url: URL(string: "https://www.myurl")!)
let allowExpectation = expectation(description: "Allows action")
viewController.webView(WKWebView(), decidePolicyFor: action) { policy in
XCTAssertEqual(policy, .allow)
allowExpectation.fulfill()
}
waitForExpectations(timeout: 1.0)
}
However on this line viewController.webView(WKWebView(), decidePolicyFor: action) I am getting below error.
Cannot call value of non-function type 'WKWebView?'
Swift version 5. How to get rid of this error? Your suggestions highly appreciated.
I'm almost sure that this is related to you adding implementation for another method of WKNavigationDelegate to your vc. It has few of them containing decidePolicyFor parameter label and if you will omit the implementation for the one that you are trying to call in here you won't be able to do so.
The reason of that (to my understanding) is that these are methods marked as optional on the protocol so your class doesn't need to implement them and if compiler won't find implementation of the method that you try to call it will complete with the error.
Since you shared a concrete example, you're basing your solution on check if your vc is having a method with exactly this signature:
extension ViewController: WKNavigationDelegate {
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
// ...
}
}

Blocked a frame with origin

I am trying to load javascript to a webview to change color of frame. I am getting "Blocked a frame with origin" error while javascript is being applied.
URL :
https://checkout-testing.herokuapp.com/v3/hosted/pay/7f4d4fd48adde85420e3
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let changeHtmlbuttonScript = """
document.getElementById(checkout).style.backgroundColor = "#0331FC"
"""
webView.evaluateJavaScript(changeHtmlbuttonScript) { (success, error) in
print("Error: \(error)")
}
}
I have tried setting "Arbitrary loads" property to false but no success. Does anyone know how to solve this.

How can I obtain POST request body from WKWebView in Swift?

I have set up an API to respond to a POST request. Due to the nature of my app, I need to fulfill this POST request through a WKWebView, as opposed to using URLSession or Alamofire. I plan to use the data POST response body elsewhere in my app.
I was able to successfully construct a post request and load it in the following way:
request = URLRequest(url: previouslyDefinedApiURL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = previouslyDefinedBodyData
myWebView.load(request)
The code above works perfectly. My web view even displays the correct response from my API.
I implemented the WKNavigationDelegate method to which my code hooks into upon loading the request.
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
if let response = navigationResponse.response as? HTTPURLResponse {
// Somehow get the response body?
}
decisionHandler(.allow)
}
The navigation response is of type URLResponse, which offers no way for extracting the body content of the response, which is a simple JSON. Somthing like the following:
{
status: "SUCCESS",
user_id: 1234,
transition_to: 'tabs'
}
Is there a swifty way of obtaining the response body from the wkwebview up in the native side of the code?
I'm answering my own question because I figured out how to do this on my own. Perhaps my question wasn't clear. My end goal was to extract the body content as a string.
Firstly, I ditched the callback I used:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
// do stuff
}
And instead, I used this callback and executed some javascript on the result:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.getElementById(\"my-id\").innerHTML", completionHandler: { (jsonRaw: Any?, error: Error?) in
guard let jsonString = jsonRaw as? String else { return }
let json = JSON(parseJSON: jsonString)
// do stuff
})
}
The id "my-id" comes from the response I constructed on the back end of this service, just in case the web view wrapped any other HTML around my response. My original intention was to do this without having to run any javascript, but I guess there's no other way. This works pretty well anyway.

How do I get WKWebView.evaluateJavaScript to return data in a function call

I'm working on some WKWebView parsing routines. I'm trying to validate that I've navigated to a page properly by checking it's document.title. I wrote a function to do this work, but I can't seem to figure out how to return the HTML data from the function or do the evaluation in the function and return a BOOL. I know I'm doing an async call here, but not sure how to wait for that call to end and feed the response back from my function call.
Here is my function:
func checkTitle (forWebView webView: WKWebView, title: String) -> String{
webView.evaluateJavaScript("document.title", completionHandler: { (innerHTML, error ) in
let titleString = innerHTML as? String
return (titleString)
})
This throws a compiler error. I've tried to declare the variable outside the call and then assign and return it after, but it tries to execute that before the async call is complete.
you should use a completion handler, something like this:
func checkTitle (forWebView webView: WKWebView, title: String, completion: #escaping (_ titleString: String?) -> Void) {
webView.evaluateJavaScript("document.title", completionHandler: { (innerHTML, error ) in
// Logic here
completion(innerHTML as? String)
})
}

how to Add timeout for WKWebview

How to write a timeout handler for WKWebView, when default delegates are not getting called for didFailNavigation.
WKWebView delegate are set & DidFinishNavigation or didFailProvisionalNavigation is getting called.
Use the error.code value of the error that didFailProvisionalNavigation creates and add your 'handler' code there:
func webView(webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: NSError) {
if error.code == -1001 { // TIMED OUT:
// CODE to handle TIMEOUT
} else if error.code == -1003 { // SERVER CANNOT BE FOUND
// CODE to handle SERVER not found
} else if error.code == -1100 { // URL NOT FOUND ON SERVER
// CODE to handle URL not found
}
}
Use this delegate method
webView:didFailProvisionalNavigation:withError:
Document
Invoked when an error occurs while starting to load data for the main frame.
And check the error code
NSURLErrorTimedOut = -1001
All the error code list
One possible solution is to add custom timer, which starts as you call loadHTML, loadRequest methods and times out on custom interval
Compared to Timer , asyncAfter(deadline:) is more light-weighted.
var isTimeOut = true
DispatchQueue.main.asyncAfter(deadline: .now() + timeOut) {
if isTimeOut{
// do time out thing
}
}
check isTimeOut according to WKNavigationDelegate
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){
isTimeOut = false
}

Resources