Hybrid Webview JS Alert

MDemirMDemir USMember ✭✭

Hi everyone,

I'm using Hybrid Webview with EventRegister , Invoke JS , EvaluateJavascript functions. As you know Hybrid webview using WKWebview. But WkWebview didnt manage and support JS Alert , prompt etc. . But also i notice WebViewRenderer(from xamarin.Forms) support JS alert, prompt etc. Do you have a way to bundle this 2 webview or do you have a any suggestions to My HybridWebview ?

Best Answers

  • MDemirMDemir US ✭✭
    Accepted Answer

    Thanks @LandLu for massive helps. I changed MyCystıneDelegate like this;

    `
    public class MyWkWebViewDelegate : WKUIDelegate
    {

        [Foundation.Export("webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:")]
        public override void RunJavaScriptAlertPanel(WebKit.WKWebView webView, string message, WebKit.WKFrameInfo frame, System.Action completionHandler)
        {
            // Create and present a native UIAlertController with the message
            var alertController = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
            alertController.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, null));
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alertController, true, null);
    
            // Call the completion handler 
            completionHandler();
        }
    
        // Called when a Javascript confirm() alert is called in the WKWebView
        // The alert panel should display the message with two buttons - "OK" and "Cancel"
        [Export("webView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler:")]
        public override void RunJavaScriptConfirmPanel(WKWebView webView, string message, WKFrameInfo frame, Action<bool> completionHandler)
        {
            // Create a native UIAlertController with the message
            var alertController = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
    
            // Add two actions to the alert. Based on the result we call the completion handles and pass either true or false
            alertController.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, okAction => {
                completionHandler(true);
            }));
            alertController.AddAction(UIAlertAction.Create("Cancel", UIAlertActionStyle.Default, cancelAction => {
                completionHandler(false);
            }));
    
            // Present the alert
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alertController, true, null);
        }
    
        // Called when a Javascript prompt() alert is called in the WKWebView
        // The alert panel should display the prompt, default placeholder text and two buttons - "OK" and "Cancel"
        [Foundation.Export("webView:runJavaScriptTextInputPanelWithPrompt:defaultText:initiatedByFrame:completionHandler:")]
        public override void RunJavaScriptTextInputPanel(WebKit.WKWebView webView, string prompt, string defaultText, WebKit.WKFrameInfo frame, System.Action<string> completionHandler)
        {
            // Create a native UIAlertController with the message
            var alertController = UIAlertController.Create(null, prompt, UIAlertControllerStyle.Alert);
    
            // Add a text field to the alert, set the placeholder text and keep a refernce to the field
            UITextField alertTextField = null;
            alertController.AddTextField((textField) => {
                textField.Placeholder = defaultText;
                alertTextField = textField;
            });
    
            // Pass the text to the completion handler when the "OK" button is tapped
            alertController.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, okAction => {
                completionHandler(alertTextField.Text);
            }));
    
            // If "Cancel" is tapped, we can just return null
            alertController.AddAction(UIAlertAction.Create("Cancel", UIAlertActionStyle.Default, cancelAction => {
                completionHandler(null);
            }));
    
            // Present the alert
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alertController, true, null);
        }
    }
    

    `

    And set like @LandLu said that. Its works like a charm.

Answers

  • LandLuLandLu Member, Xamarin Team Xamurai
    edited November 2018

    You can implement the WKUIDelegate to achieve the alert and prompt:

    public class MyWKWebViewDelegate : WKUIDelegate
    {
        UIViewController ViewController;
        public MyWKWebViewDelegate(UIViewController viewController)
        {
            ViewController = viewController;
        }
        public override void RunJavaScriptAlertPanel(WKWebView webView, string message, WKFrameInfo frame, Action completionHandler)
        {
            var alertController = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
            alertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, (action) =>
            {
    
            }));
    
            ViewController.PresentViewController(alertController, true, null);
        }
    
        public override void RunJavaScriptConfirmPanel(WKWebView webView, string message, WKFrameInfo frame, Action<bool> completionHandler)
        {
            // Confirm
        }
    
        public override void RunJavaScriptTextInputPanel(WKWebView webView, string prompt, string defaultText, WKFrameInfo frame, Action<string> completionHandler)
        {
            // Prompt
        }
    }
    

    Do not forget to set this delegate to the wkwebview in your custom renderer.

  • MDemirMDemir USMember ✭✭

    @LandLu How can I set this delegate to my customrenderer wkwebview ?

    Here is my custom renderer;

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Foundation;
    using Paperwork.Mobile.CustomControls;
    using Paperwork.Mobile.iOS.CustomRenderers;
    using WebKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;

    [assembly: ExportRenderer(typeof(PWHybridWebView), typeof(PWHybridWebViewRenderer))]

    namespace Paperwork.Mobile.iOS.CustomRenderers
    {
    public class PWHybridWebViewRenderer : ViewRenderer<PWHybridWebView, WKWebView>, IWKScriptMessageHandler,
    IWKNavigationDelegate
    {
    private const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
    private WKUserContentController userController;

        public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
        {
            Element.InvokeAction(message.Body.ToString());
        }
    
        protected override void OnElementChanged(ElementChangedEventArgs<PWHybridWebView> e)
        {
            base.OnElementChanged(e);
    
            if (Control == null)
            {
                userController = new WKUserContentController();
    
                var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
                userController.AddUserScript(script);
                userController.AddScriptMessageHandler(this, "invokeAction");
    
                var config = new WKWebViewConfiguration { UserContentController = userController };
                var webView = new WKWebView(Frame, config);
    
                webView.Configuration.Preferences.JavaScriptEnabled = true;
                webView.Configuration.Preferences.JavaScriptCanOpenWindowsAutomatically = true;
    
                SetNativeControl(webView);
            }
    
            if (e.OldElement != null)
            {
                userController.RemoveAllUserScripts();
                userController.RemoveScriptMessageHandler("invokeAction");
                var hybridWebView = e.OldElement;
                hybridWebView.Cleanup();
            }
    
            if (e.NewElement != null)
            {
                Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
                var webView = e.NewElement;
    
                webView.EvaluateJavascript = async js =>
                        {
                            var reset = new ManualResetEvent(false);
                            Device.BeginInvokeOnMainThread(async () =>
                            {
                                try
                                {
                                    await Control.EvaluateJavaScriptAsync(js);
                                }
                                catch (Exception)
                                {
                                        //
                                    }
                            });
                            await Task.Run(() => { reset.WaitOne(); });
                            return null;
                        };
            }
        }
    }
    

    }

  • MDemirMDemir USMember ✭✭

    @LandLu How can I set this delegate to the wkwebview in my custom renderer ?

  • MDemirMDemir USMember ✭✭
    Accepted Answer

    Thanks @LandLu for massive helps. I changed MyCystıneDelegate like this;

    `
    public class MyWkWebViewDelegate : WKUIDelegate
    {

        [Foundation.Export("webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:")]
        public override void RunJavaScriptAlertPanel(WebKit.WKWebView webView, string message, WebKit.WKFrameInfo frame, System.Action completionHandler)
        {
            // Create and present a native UIAlertController with the message
            var alertController = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
            alertController.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, null));
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alertController, true, null);
    
            // Call the completion handler 
            completionHandler();
        }
    
        // Called when a Javascript confirm() alert is called in the WKWebView
        // The alert panel should display the message with two buttons - "OK" and "Cancel"
        [Export("webView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler:")]
        public override void RunJavaScriptConfirmPanel(WKWebView webView, string message, WKFrameInfo frame, Action<bool> completionHandler)
        {
            // Create a native UIAlertController with the message
            var alertController = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
    
            // Add two actions to the alert. Based on the result we call the completion handles and pass either true or false
            alertController.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, okAction => {
                completionHandler(true);
            }));
            alertController.AddAction(UIAlertAction.Create("Cancel", UIAlertActionStyle.Default, cancelAction => {
                completionHandler(false);
            }));
    
            // Present the alert
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alertController, true, null);
        }
    
        // Called when a Javascript prompt() alert is called in the WKWebView
        // The alert panel should display the prompt, default placeholder text and two buttons - "OK" and "Cancel"
        [Foundation.Export("webView:runJavaScriptTextInputPanelWithPrompt:defaultText:initiatedByFrame:completionHandler:")]
        public override void RunJavaScriptTextInputPanel(WebKit.WKWebView webView, string prompt, string defaultText, WebKit.WKFrameInfo frame, System.Action<string> completionHandler)
        {
            // Create a native UIAlertController with the message
            var alertController = UIAlertController.Create(null, prompt, UIAlertControllerStyle.Alert);
    
            // Add a text field to the alert, set the placeholder text and keep a refernce to the field
            UITextField alertTextField = null;
            alertController.AddTextField((textField) => {
                textField.Placeholder = defaultText;
                alertTextField = textField;
            });
    
            // Pass the text to the completion handler when the "OK" button is tapped
            alertController.AddAction(UIAlertAction.Create("Ok", UIAlertActionStyle.Default, okAction => {
                completionHandler(alertTextField.Text);
            }));
    
            // If "Cancel" is tapped, we can just return null
            alertController.AddAction(UIAlertAction.Create("Cancel", UIAlertActionStyle.Default, cancelAction => {
                completionHandler(null);
            }));
    
            // Present the alert
            UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alertController, true, null);
        }
    }
    

    `

    And set like @LandLu said that. Its works like a charm.

  • MDemirMDemir USMember ✭✭

    Here is my custom renderer

    `using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Foundation;
    using Paperwork.Mobile.CustomControls;
    using Paperwork.Mobile.iOS.CustomRenderers;
    using WebKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;

    [assembly: ExportRenderer(typeof(PWHybridWebView), typeof(PWHybridWebViewRenderer))]

    namespace Paperwork.Mobile.iOS.CustomRenderers
    {
    public class PWHybridWebViewRenderer : ViewRenderer<PWHybridWebView, WKWebView>, IWKScriptMessageHandler,
    IWKNavigationDelegate
    {
    private const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
    private WKUserContentController userController;

        public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
        {
            Element.InvokeAction(message.Body.ToString());
        }
    
        protected override void OnElementChanged(ElementChangedEventArgs<PWHybridWebView> e)
        {
            base.OnElementChanged(e);
    
            if (Control == null)
            {
                userController = new WKUserContentController();
    
                var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
                userController.AddUserScript(script);
                userController.AddScriptMessageHandler(this, "invokeAction");
    
                var config = new WKWebViewConfiguration { UserContentController = userController };
                var webView = new WKWebView(Frame, config);
    
                webView.Configuration.Preferences.JavaScriptEnabled = true;
                webView.Configuration.Preferences.JavaScriptCanOpenWindowsAutomatically = true;
    
                SetNativeControl(webView);
            }
    
            if (e.OldElement != null)
            {
                userController.RemoveAllUserScripts();
                userController.RemoveScriptMessageHandler("invokeAction");
                var hybridWebView = e.OldElement;
                hybridWebView.Cleanup();
            }
    
            if (e.NewElement != null)
            {
                Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
                var webView = e.NewElement;
    
                webView.EvaluateJavascript = async js =>
                        {
                            var reset = new ManualResetEvent(false);
                            Device.BeginInvokeOnMainThread(async () =>
                            {
                                try
                                {
                                    await Control.EvaluateJavaScriptAsync(js);
                                }
                                catch (Exception)
                                {
                                        //
                                    }
                            });
                            await Task.Run(() => { reset.WaitOne(); });
                            return null;
                        };
            }
        }
    }
    

    }`

  • MDemirMDemir USMember ✭✭

    How can I set this delegate to the wkwebview in my custom renderer ?

    here is my custom renderer

    `using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Foundation;
    using Paperwork.Mobile.CustomControls;
    using Paperwork.Mobile.iOS.CustomRenderers;
    using WebKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;

    [assembly: ExportRenderer(typeof(PWHybridWebView), typeof(PWHybridWebViewRenderer))]

    namespace Paperwork.Mobile.iOS.CustomRenderers
    {
    public class PWHybridWebViewRenderer : ViewRenderer<PWHybridWebView, WKWebView>, IWKScriptMessageHandler,
    IWKNavigationDelegate
    {
    private const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
    private WKUserContentController userController;

        public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
        {
            Element.InvokeAction(message.Body.ToString());
        }
    
        protected override void OnElementChanged(ElementChangedEventArgs<PWHybridWebView> e)
        {
            base.OnElementChanged(e);
    
            if (Control == null)
            {
                userController = new WKUserContentController();
    
                var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
                userController.AddUserScript(script);
                userController.AddScriptMessageHandler(this, "invokeAction");
    
                var config = new WKWebViewConfiguration { UserContentController = userController };
                var webView = new WKWebView(Frame, config);
    
                webView.Configuration.Preferences.JavaScriptEnabled = true;
                webView.Configuration.Preferences.JavaScriptCanOpenWindowsAutomatically = true;
    
                SetNativeControl(webView);
            }
    
            if (e.OldElement != null)
            {
                userController.RemoveAllUserScripts();
                userController.RemoveScriptMessageHandler("invokeAction");
                var hybridWebView = e.OldElement;
                hybridWebView.Cleanup();
            }
    
            if (e.NewElement != null)
            {
                Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
                var webView = e.NewElement;
    
                webView.EvaluateJavascript = async js =>
                        {
                            var reset = new ManualResetEvent(false);
                            Device.BeginInvokeOnMainThread(async () =>
                            {
                                try
                                {
                                    await Control.EvaluateJavaScriptAsync(js);
                                }
                                catch (Exception)
                                {
                                        //
                                    }
                            });
                            await Task.Run(() => { reset.WaitOne(); });
                            return null;
                        };
            }
        }
    }
    

    }
    `

Sign In or Register to comment.