Lesser-Known WKWebView Features
Recently, I worked on a freelance project (Aged Care Decisions) where I was asked to build a PoC set of white-label apps that uses web views throughout. Below are the things I discovered while implementing WKWebView in the iOS apps.
iOS and Web have a long history. Their history can be defined in two eras: the shaky rule of UIWebView
followed by the savior, WKWebView
. UIWebView
has been deprecated since iOS 12. Apple won’t even accept app submissions if there’s even a trace of it. Why would they, when its successor performs twice as well?
WKWebView
is a part of the WebKit
framework and runs outside the application’s main thread, thus contributing to its stability and superior performance.
For starters, to load content, let’s say a URL string in a WKWebView
, we simply do the following:
guard let url = URL(string: string) else { return }
let request = URLRequest(url: url)
webView?.load(request)
There’s a lot more you can do with WKWebView
than just content loading and CSS styling.
The following section is a checklist of the relatively lesser-known features of WKWebView
.
1. Intercepting Web URL
By implementing WKNavigationDelegate
protocol’s decidePolicyFor
function we can intercept intermediate URL during navigations. The following code snippet shows how it’s done:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
let urlString = navigationAction.request.url?.absoluteString ?? ""
let pattern = "interceptSomeUrlPattern"
if urlString.contains(pattern){
var splitPath = urlString.components(separatedBy: pattern)
}
}
2. JavaScript Alerts
By default, prompts from JavaScript don’t show up in WKWebView
since it isn’t a part of UIKit. So, we need to implement the WKUIDelegate
protocol in order to show alerts, confirmations or text input in prompts.
Following are the methods for each of the different alerts or action sheets:
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void)
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void)
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.text = defaultText
}
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
if let text = alertController.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
self.present(alertController, animated: true, completion: nil)
}
3. Configure URL Actions
Using the decidePolicyFor
function, you can not only control external navigation with actions such as calls, facetime, and mail but also choose to restrict certain URLs from opening. The following piece of code shows each of these cases.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
if ["tel", "sms", "mailto"].contains(url.scheme) && UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
} else {
if let host = navigationAction.request.url?.host {
if host == "www.notsafeforwork.com" {
decisionHandler(.cancel)
}
else{
decisionHandler(.allow)
}
}
}
}
}
4. Authenticating With WKWebView
When your URL in WKWebView
requires user authorization, you need to implement the following method:
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let authenticationMethod = challenge.protectionSpace.authenticationMethod
if authenticationMethod == NSURLAuthenticationMethodDefault || authenticationMethod == NSURLAuthenticationMethodHTTPBasic || authenticationMethod == NSURLAuthenticationMethodHTTPDigest {
//Do you stuff
}
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
}
On receiving the authentication challenge you can determine the type of authentication it needs (user credentials or a certificate) and handle the conditions with prompts or predefined credentials accordingly
5. Sharing Cookies Across WKWebViews
Every instance of WKWebView
has its own cookie storage. In order to share cookies across multiple instances of WKWebView
, we need to use WKHTTPCookieStore
as shown below:
let cookies = HTTPCookieStorage.shared.cookies ?? []for (cookie) in cookies { webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)}
Other features of WKWebView, such as showing progress updates of the URL being loaded, are fairly common these days.
Bonus: ProgressViews
can be updated by listening to the estimatedProgress
keyPath
value of the following method:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)