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

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.

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) {
// ...
}
}

how do I perform tasks before loading a webview url in ios?

so I can't seem to figure out a way to perform some tasks before loading a url in ios webview.
Like in android we can use shouldOverrideUrlLoading.
#Override
public boolean shouldOverrideUrlLoading(final WebView view, String url) {
///sometask
}
but I can't find a function that does something like this in ios.
Say a user clicked on a href tag inside the webview that takes it to different page, how do I know what link that tag takes the user to? before changing the page.
Basically I want to check what url the webpage is about to load and perform extra steps according to that url.
You can use WKNavigationDelegate
set navigationDelegate in viewDidLoad
self.webView?.navigationDelegate = self
Observe URL in below navigationDelegate Method.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
DispatchQueue.main.async {
if let urlString = navigationAction.request.url?.absoluteString{
print(urlString)
}
}
decisionHandler(.allow)
}

WKWebView Localization iOS

I am loading a URL in WKWebview in which I am passing "Accept-Language" header for displaying page as per passed language.
var aRequest = URLRequest.init(url: MyUrl)
aRequest.setValue("fr-CA", forHTTPHeaderField: "Accept-Language")
self.webView.load(aRequest)
The above works properly in case of one of the URL and doesn't work in case of other. How to correctly use the header of Accept-Language?
I got inspired by How to set custom HTTP headers to requests made by a WKWebView and played around a bit with it.
This is what I ended up with:
Set the navigationDelegate of the webView to self and implement
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard navigationAction.request.value(forHTTPHeaderField: "Accept-Language") != "fr-CA" && navigationAction.navigationType != .other else {
decisionHandler(.allow)
return
}
decisionHandler(.cancel)
var request = navigationAction.request
request.setValue("fr-CA", forHTTPHeaderField: "Accept-Language")
webView.load(request)
}
The locales which worked for me are as below:
"fr-CA" - French
"es" - Spanish
"zh-CN" - Chinese

How to catch httpBody in WKWebView decidePolicyForNavigationAction?

I switched from UIWebView to WKWebView in an iOS application. The webview loads a remote document which contains a POST-formular. Once the form is submitted a PDF is generated via PHP.
To be able to download and show this PDF properly I need to catch the POST content when the form is submitted - and do a manual URLRequest to download the file
I can catch the request as follows but the httpBody is empty in the code below:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
var request = navigationAction.request
if (request.httpMethod == "POST") {
request.httpBody // <-- is EMPTY
// as the httpBody is empty, the request below does not get the required result anymore
Alamofire.request(request).responseData { response in
...
let pdfData : Data = response.result.value!
...
let docController = UIDocumentInteractionController(url: urlToLocalPdfFile)
docController.delegate = self
docController.presentPreview(animated: true)
}
}
}
This did work with the UIWebView before. Is there a way to access the httpBody in the code above, or any workaround to accomplish the PDF-download (as described above)?

how to get cookie from urlrequest in iOS wkwebview

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
var req = navigationAction.request;
let cookie = req.value(forHTTPHeaderField: "Cookie");
print(cookie) // always nil
decisionHandler(.allow);
}
I want to get the session after user login, but the cookie is always nil;how can I get it ?
Cookie is generally stored in variable document.cookie in browser.
You can access it by executing Javascript code. In this case simply "document.cookie" would return the cookies.
let cookieScript = "document.cookie;"
webView.evaluateJavaScript(cookieScript) { (response, error) in
if let response = response {
print(response as! String)
}
}
Why don't use WKWebView's API that appeared since iOS 11
webView.configuration.websiteDataStore.httpCookieStore.getAllCookies() { cookies in
// do what you need with cookies
}
One advantage is that this method tracks HttpOnly cookies as well.

Resources