UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How do a send back the result of an Alert confirmation from ContentView back to a Coordinator of a UIViewRepresentable?

Forums > 100 Days of SwiftUI

Hi,

I have a UIViewRepresentable for a WKWebView which is loaded in ContentView, all is well and I can respond to UI and Navigation delegate methods.

Part of what I want to do is raise Alert when JavaScript raises events (which is handled fine by passing in a couple of Binding and setting the wrappedValue). I am be able to raise Alerts with confirmation buttons in a similar fashion.

The runJavaScriptConfirmPanelWithMessage delegate method called by WKWebView excepts to call completionHandler with a Boolean value which would be the result of what the user tapped on the Alert.

Question What would be the ideal way to bubble back the the resposne from the Alert back to the Coordinator housed in the ContentView ?

Any pointers are very welcome. Thank you for sparing your time to read and respond.

Reference implementation of UIViewRepresentable with Coordinator:

import Foundation
import SwiftUI
import WebKit

struct WebView : UIViewRepresentable {

    let request: URLRequest
    @Binding var showAlert: Bool
    @Binding var alertMessage: String

    // This has to be inside the representable structure
    class Coodinator: NSObject, WKUIDelegate, WKNavigationDelegate {

        var parent: WebView
        var showAlert: Binding<Bool>
        var alertMessage: Binding<String>

        init(_ parent: WebView, showAlert: Binding<Bool>, alertMessage: Binding<String>) {
            self.parent = parent
            self.showAlert = showAlert
            self.alertMessage = alertMessage
        }

        // MARK: - UI Delegate

        func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
            self.alertMessage.wrappedValue = message
            self.showAlert.wrappedValue.toggle()
            completionHandler()
        }

        func webView(_ webView: WKWebView,
                     runJavaScriptConfirmPanelWithMessage message: String,
                     initiatedByFrame frame: WKFrameInfo,
                     completionHandler: @escaping (Bool) -> Void) {
            self.alertMessage.wrappedValue = message
            self.showAlert.wrappedValue.toggle()
            completionHandler(true)
        }

        // MARK: - Navigation Delegate

        func webView(_ webView: WKWebView,
                     decidePolicyFor navigationAction: WKNavigationAction,
                     decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            decisionHandler(.allow)
        }

    }

    func makeCoordinator() -> Coodinator {
        return Coodinator(self, showAlert: self.$showAlert, alertMessage: self.$alertMessage)
    }

    func makeUIView(context: Context) -> WKWebView  {
        let webview = WKWebView()
        webview.uiDelegate = context.coordinator
        webview.navigationDelegate = context.coordinator
        return webview
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.load(request)
    }
}

and extract from the CotnentView that houses it:

    var body: some View {
        NavigationView {
            VStack {
                WebView(request: URLRequest(url: URL(string: "https://.....")!), showAlert: self.$showAlert, isAlertConfirmation: self.$isAlertConfirmation, alertMessage: self.$alertMessage)
                .alert(isPresented: self.$showAlert) { () -> Alert in
                    var alert = Alert(title: Text(alertMessage))
                    if(self.isAlertConfirmation == true) {
                        alert = Alert(title: Text("Are you Sure?"), message: Text(alertMessage), primaryButton: .default(Text("Confirm"), action: {
                            print("OK")
                        }), secondaryButton: .cancel({
                            print("Cancel")
                        }))
                    }
                    return alert;
                }
            }

4      

I have exactly this same problem! Did you manage to find a solution?

4      

Same problem and can't find solution. Help! :)

3      

I found solution by using UIAlertController instead of SwiftUI alert. Try this code:

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

      if let controller = topMostViewController() {
          controller.present(alertController, animated: true, completion: nil)
      }

}

private func topMostViewController() -> UIViewController? {
    guard let rootController = keyWindow()?.rootViewController else {
        return nil
    }
    return topMostViewController(for: rootController)
}

private func keyWindow() -> UIWindow? {
    return UIApplication.shared.connectedScenes
    .filter {$0.activationState == .foregroundActive}
    .compactMap {$0 as? UIWindowScene}
    .first?.windows.filter {$0.isKeyWindow}.first
}

private func topMostViewController(for controller: UIViewController) -> UIViewController {
    if let presentedController = controller.presentedViewController {
        return topMostViewController(for: presentedController)
    } else if let navigationController = controller as? UINavigationController {
        guard let topController = navigationController.topViewController else {
            return navigationController
        }
        return topMostViewController(for: topController)
    } else if let tabController = controller as? UITabBarController {
        guard let topController = tabController.selectedViewController else {
            return tabController
        }
        return topMostViewController(for: topController)
    }
    return controller
}

Thanks to this answer https://stackoverflow.com/a/57877120/11212894

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.