May 3, 2021

Confirm dialogs in iOS webviews using Turbo 

When starting out with Turbo-iOS, you'll notice that most stuff will work out of the box. Two things that don't work out of the box are alerts and confirm dialogs. Luckily, with the help of Swift, it's easy to add to your hybrid app.
In a previous article I showed you how to create a Turbo-iOS app with a native tab bar. The examples below use that app as a starting point.

Add a UIDelegate to your webView

Inside the SessionDelegate there's a function called sessionDidLoadWebView. Add a line that sets the uiDelegate to self. Your function will look like this:
func sessionDidLoadWebView(_ session: Session) {
    session.webView.navigationDelegate = self
    session.webView.uiDelegate = self
}

Adding alerts

Create a new extension for your ApplicationViewController.
extension ApplicationViewController: WKUIDelegate {
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping () -> Void) {

        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
            completionHandler()
        }))

        present(alertController, animated: true, completion: nil)
    }
}
This simple extension creates a new alertController and presents it with a simple OK button. Now when your JavaScript calls alert();, it'll show an alert box right inside your webview. Just like it would in Safari.

Adding confirm dialogs

Alerts can be useful, but what's even more helpful is a confirm dialog. These are especially great for delete confirmations. 

You can add them directly to the WKUIDelegate extension:
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
             completionHandler: @escaping (Bool) -> Void) {

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

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

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

    present(alertController, animated: true, completion: nil)
}
In this case I've chosen to add Delete and Cancel buttons to the dialog, but you can also swap those out for Confirm or any other label you prefer. Notice that I've set the primary button's style to .destructive. This gives the delete button a distinctive red color, highlighting that what you're doing is destructive.

Using Stimulus for confirm boxes

In your javascript, you can now use confirm() to show native confirm boxes on iOS. Here's a nice little Stimulus controller to easily add this functionality to any button or link:
import { Controller } from "stimulus"

export default class extends Controller {
  
  confirm(e) {
    if (!confirm("Are you sure?")) {
      e.preventDefault()
      e.stopPropagation()
    }
  }
  
}