How to handle popups and alerts in WKWebView

How to handle popups and alerts in WKWebView

Β·

3 min read

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 attribute target="_blank"
  • opening a JavaScript alert by clicking on an <a> HTML element with href="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.

Doesn't work without delegate implementation

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

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 // !!
JavaScriptTriggered 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.

Working with delegate implementation

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!