In this blog post, I'll explain how you can support popups and alerts in WKWebView because this is not supported out of the box.
Default behavior
The following cases do not work in a WKWebView
automatically:
- opening a new tab/window with
<a>
HTML element with attributetarget="_blank"
- opening a JavaScript alert by clicking on an
<a>
HTML element withhref="javascript:alert('Hello World!');"
- opening a new window with Javascript and DOM's
window.open
and window.close` functions
You can verify that yourself by using this HTML page to manually test the three use cases.
<!DOCTYPE html>
<html>
<body>
<h1>WKWebView and WKUIDelegate</h1>
<h2>Testing a href with target="_blank"</h2>
<a href="https://www.google.com" target="_blank">Visit Google</a>
<h2>Testing a href with javascript:alert</h2>
<a href="javascript:alert('Hello World!');">Execute JavaScript</a>
<h2>Testing window.open() and window.close() Methods</h2>
<button onclick="openWin()">Open "myWindow" which will be closed after 3 seconds</button>
<script>
function openWin() {
myWindow = window.open("https://www.apple.com", "", "width=200,height=100");
setTimeout(function() { myWindow.close() }, 3000);
}
</script>
</body>
</html>
We start with a WKWebView
without any delegate implementation.
let webView = WKWebView()
And let's load the HTML page, which can be bundled as a resource in an iOS application.
if let url = Bundle.main.url(forResource: "local", withExtension: "html") {
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
}
Clicking on any other links or buttons has no effect.
Why is that?
Introducing WKUIDelegate
The default web view implementation assumes one window per web view, so nonconventional user interfaces might implement a user interface delegate.
This quote is from the WKUIDelegate
documentation.
WKUIDelegate
is a protocol with several optional functions that can be implemented.
Web view user interface delegates implement this protocol to control the opening of new windows, augment the behavior of default menu items displayed when the user clicks elements, and perform other user interface-related tasks. These methods can be invoked as a result of handling JavaScript or other plug-in content.
extension ViewController: WKUIDelegate {}
let webView = WKWebView()
webView.uiDelegate = self // or whoever conforms to WKUIDelegate protocol
Handle static HTML links
Let's implement the first function: webView(_:createWebViewWith:for:windowFeatures:)
If you do not implement this method, the web view will cancel the navigation.
A rudimentary implementation can be that such links are opened in the Safari app.
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
UIApplication.shared.open(navigationAction.request.url!, options: [:])
}
return nil
}
Later comes a more sophisticated example to allow handling navigation actions with the app itself. So keep on reading :)
JavaScript alerts
First, you have to configure your WKWebView to allow JavaScript !!
let webView = WKWebView()
webView.uiDelegate = self
webView.configuration.preferences.javaScriptEnabled = true // !!
Then it's time to implement one of the runJavascript
related functions in WKUIDelegate
.
For the test case above the function webView(_:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:)
needs to be implemented.
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let ac = UIAlertController(title: nil,
message: message,
preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "Ok",
style: .default) { _ in
completionHandler()
})
present(ac, animated: true)
}
JavaScript popups
First, you have to configure your WKWebView that JavaScript can open windows !!
let webView = WKWebView()
webView.uiDelegate = self
webView.configuration.preferences.javaScriptEnabled = true
webView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true // !!
JavaScript | Triggered WKUIDelegate function |
window.open() | webView(_:createWebViewWith:for:windowFeatures:) |
window.close() | webViewDidClose(_:) |
You can achieve an in-app popup experience by implementing those two delegate functions and managing multiple WKWebInstances
. For more details and an example project please check out the following GitHub repository:
Conclusion
You can support popups and alerts in a WKWebView
by implementingWKUIDelegate
.