16

I am using WKWebView to open up example.com, and on there I have a test link which is supposed to open up a JS alert, but I can't get it to display on the device, it only works if I view the site from browser.

I am using WKUIDelegate, and added this piece of code to the ViewController.swift file:

func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: (() -> Void)) {

    NSLog("Hello")
}

I don't see anything in the XCode console when I click the link that spawns the JS alert.

What am I missing?

Onato
  • 9,916
  • 5
  • 46
  • 54
Mike Purcell
  • 19,847
  • 10
  • 52
  • 89
  • [ios wkwebview now showing javascript alert dialog](https://stackoverflow.com/q/26898941/6521116) – LF00 Jun 01 '17 at 03:04

3 Answers3

32

A little late but I would like to add my experience for future reference. The answer of @Bon Bon brought me on the path to the solution while I was trying to make things work with Swift 3 and IOS 10, in which case the code needs some modifications. Firstly you need to implement also WKUIDelegate, so add it to the ViewController declaration:

class ViewController: UIViewController, WKUIDelegate {

Then when you instantiate the WKWebView object, as for example like so:

self.webView = WKWebView(frame: self.view.frame)

you need also to assign the correct value to the uiDelegate property of the instance:

self.webView?.uiDelegate = self

Then finally you can use the code provided by @Bon Bon, but note that there are some little differences required by Swift 3, as for example, the name of the presentViewController method becomes present :

func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {

    let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)

    alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
        completionHandler()
    }))

    self.present(alertController, animated: true, completion: nil)
}

func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {

    let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)

    alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
        completionHandler(true)
    }))

    alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
        completionHandler(false)
    }))

    self.present(alertController, animated: true, completion: nil)
}

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)
        }

    }))

    alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in

        completionHandler(nil)

    }))

    self.present(alertController, animated: true, completion: nil)
}

That made alert, confirmation and text input to work correctly within WKWebView, without any compiler warning in Xcode 8. I'm not an expert Swift programmer, so any useful comment about correctness of the code would be very appreciated.

lighter
  • 2,808
  • 3
  • 40
  • 59
wiredolphin
  • 1,431
  • 1
  • 18
  • 26
  • 1
    Thanks to you @Bon Bon for your original code, without it I would have had a long night of work! – wiredolphin Oct 20 '16 at 17:40
  • 2
    `preferredStyle: .alert` should be set for prompt instead `preferredStyle: .actionSheet` - without this change I'm getting error saying text field could be added only to alert – godblessstrawberry Sep 27 '17 at 22:14
  • 2
    It appears prompt() fails with signal SIGABRT for me using Swift 4 unless you apply godblessstrawberry comment. @wiredolphin may be good to change your answer correction to use preferredStyle: .alert – Raymond Naseef Feb 27 '18 at 09:26
  • 1
    this should be the selected answer – kchoi Jul 28 '19 at 04:56
  • For iPad preferredStyle must be .alert because otherwise an exception will be thrown – Jerzy Kiler Jan 24 '22 at 15:02
20

You also need to set the uiDelegate on the WKWebView.

import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {

    var wkWebView: WKWebView!

    public override func viewDidLoad() {
        super.viewDidLoad()

        wkWebView = WKWebView(frame: view.bounds, configuration: WKWebViewConfiguration())
        wkWebView.uiDelegate = self
        wkWebView.navigationDelegate = self
        view.addSubview(wkWebView!)
        let url = URL(string: "https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_alert")!
        wkWebView.load(URLRequest(url: url))
    }

    func webView(_ webView: WKWebView,
                 runJavaScriptAlertPanelWithMessage message: String,
                 initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping () -> Void) {

        let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        let title = NSLocalizedString("OK", comment: "OK Button")
        let ok = UIAlertAction(title: title, style: .default) { (action: UIAlertAction) -> Void in
            alert.dismiss(animated: true, completion: nil)
        }
        alert.addAction(ok)
        present(alert, animated: true)
        completionHandler()
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        wkWebView.evaluateJavaScript("alert('Hello from evaluateJavascript()')", completionHandler: nil)
    }
}

For confirm() and prompt() see the other delegate methods.

Onato
  • 9,916
  • 5
  • 46
  • 54
  • Yes this was set. Are you able to get js to work with WKWebView? I had to change all my native js alert/confirm dialogs to use bootbox dialog to get past the problem. – Mike Purcell Jan 13 '16 at 14:15
  • Yes, it works fine for me. Was your controller your UIDelegate or did you have a seperate object? Perhaps the delegate was being deallocated? – Onato Jan 13 '16 at 20:13
  • You are able to open native js dialog boxes wiht this code? – Mike Purcell Jan 14 '16 at 19:07
  • Yes. If you upload an example project to Github I can take a look why it's not working for you. – Onato Jan 14 '16 at 21:15
  • This not work for wkwebview's evaluateJavaScript(), refer to this post http://stackoverflow.com/q/43600624/6521116 – LF00 May 10 '17 at 03:37
8

Here is a sample code for various javascript alert implementation in Swift:

It's all about converting info in javascript alert into native UI, and call the completionHandler() to send the user action back to javascript engine.

func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {

        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .ActionSheet)

        alertController.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (action) in
            completionHandler()
        }))

        self.presentViewController(alertController, animated: true, completion: nil)
}

func webView(webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: (Bool) -> Void) {

        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .ActionSheet)

        alertController.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (action) in
            completionHandler(true)
        }))

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Default, handler: { (action) in
            completionHandler(false)
        }))

        self.presentViewController(alertController, animated: true, completion: nil)
}

func webView(webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: (String?) -> Void) {

        let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .ActionSheet)

        alertController.addTextFieldWithConfigurationHandler { (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)
            }

        }))

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Default, handler: { (action) in

            completionHandler(nil)

        }))

        self.presentViewController(alertController, animated: true, completion: nil)
}
  • How do you modify this code so it also works with ipad? – applecrusher Oct 19 '16 at 20:13
  • 2
    I needed the following line which I put as an edit to this answer: alertController.popoverPresentationController?.sourceView = self.view – applecrusher Oct 19 '16 at 20:31
  • @applecrusher Did you run into an issue where the app crashes if you try to show multiple alerts at once? Works great on iPhones but crashes on iPads. – Crashalot Dec 25 '17 at 01:14