How to call Webview.Eval() after Rendered?

Hi, all. I have reached a problem when using HybridWebView.

In my portable part, I have a defined a class HybridWebView : WebView and a class HybridWebViewPageCS : ContentPage. In the HybridWebViewPageCS, I claim a variable hybridWebView, which is a HybridWebView object, in the Content. I also defined some evnts that will call hybridWebView.Eval() in the HybridWebViewPageCS.

However, after I render the HybridWebView in the IOS app, it seems that the hybridWebView.Eval() in the HybridWebViewPageCS is not working.

My question is, is there anyway to define a javascript evaluation function in the HybridWebViewPageCS that will work after render?

Best Answer

Answers

  • @KieranKelly said:
    Hi, did you have any luck resolving this. I have same problem :(

    Yes, I find out that instead of running hybridWebView.Eval() in the HybridWebViewPageCS, we could run the JavaScript Evaluation in the rendered object. However, the method is a little bit different on different platforms.

    For iOS, you can try,

                                        WKJavascriptEvaluationResult handler = (NSObject result, NSError err) =>
                                        {
                                            if (err != null)
                                            {
                                                System.Console.WriteLine(err);
                                            }
                                            if (result != null)
                                            {
                                                System.Console.WriteLine(result);
                                            }
                                        };
    
                                        Control.EvaluateJavaScript(new NSString( "console.log('hello world')" ), handler);
    
  • KieranKellyKieranKelly IEMember

    Hi,
    Thanks for the quick reply. I'm still kinda new to this, so please bear with me. I kinda thought that I would have to evaluate the script on the native platform, however If I add a method to the rendered view, I cannot see how I can execute it from the shared code. i.e. If I create a method in the definition of the shared hybridWebViewRenderer i.e. no HybridViewRenderer on shared project only in platform specific one, how do I execute it from the shared code?

    One other question have primarily been working with UWP first, however I am not sure where your code would go, would you mind offering me some guidance

    Thanks

  • KieranKellyKieranKelly IEMember

    Hi,

    I created a dependency service to execute my method which contains the JavaScript eval i.e. Control.InvokeScript(javascriptFunction, parmArray), however, Control is null and Im at a loss as to how to get a reference to it. any ideas?

    Thanks

  • KieranKellyKieranKelly IEMember

    Hi, I thought Id better explain what I'm trying to do and what I've accomplished so far in the hope that you can help.

    I'm trying to create a cross platform app in a web view that will allow the user to take a photo and save it to a server. This will be part of a much larger web app that I also want to run native which is why I'm using a web view. Since most of the examples are for IOS and Android, I'm trying to get UWP working first to ensure I can get it to work.

    To date I have a PCL project with a HybridWebView extending WebView, and not view so I can access Eval, in the shared code. From the web view, via a dependency service, I am successfully calling the native CameraCapture api (Javascript calling C#) and returning the picture as url encoded base64 text to the shared code. However, when I call Eval on the HybridWebView, nothing happens.

    I tried using another dependency service, executing a method in the HybridWebViewRenderer to call the javascript but Control (and Element) are null. This is why I was asking as to where you are executing your call to Control from. Like I said I'm kinda new to this and I'm at a loss as how to call the Eval or InvokeJavascript in the native. I tried passing the HybridWebView to the HybridViewRenderer via the dependcey service, but that didnt work.

    any ideas?

  • @KieranKelly said:
    Hi, I thought Id better explain what I'm trying to do and what I've accomplished so far in the hope that you can help.

    I'm trying to create a cross platform app in a web view that will allow the user to take a photo and save it to a server. This will be part of a much larger web app that I also want to run native which is why I'm using a web view. Since most of the examples are for IOS and Android, I'm trying to get UWP working first to ensure I can get it to work.

    To date I have a PCL project with a HybridWebView extending WebView, and not view so I can access Eval, in the shared code. From the web view, via a dependency service, I am successfully calling the native CameraCapture api (Javascript calling C#) and returning the picture as url encoded base64 text to the shared code. However, when I call Eval on the HybridWebView, nothing happens.

    I tried using another dependency service, executing a method in the HybridWebViewRenderer to call the javascript but Control (and Element) are null. This is why I was asking as to where you are executing your call to Control from. Like I said I'm kinda new to this and I'm at a loss as how to call the Eval or InvokeJavascript in the native. I tried passing the HybridWebView to the HybridViewRenderer via the dependcey service, but that didnt work.

    any ideas?

    This description totally help me understand what you are doing and what's your problem.

    Actually I was going through a very similar problem with you. We do want to Eval some JavaScript Code from the shared code because we want to pass some parameters along with the JavaScript Code to the Webview (in your case, the encoded based64 text), and these parameters could only be reached in the shared code. Right?

    I come up with another solution for this. Instead of eval the JavaScript Code from the shared code, we could try to let the native app code (HybirdWebviewRenderer) got the parameter (the encoded based64 text), and eval the JavaScript Code from the native app code.

    Here is how I do this. First, this is my HybirdWebview Class:

    using System;
    using Xamarin.Forms;
    
    namespace CustomRenderer
    {
        public class HybridWebView : WebView
        {
            Action<string> action;
    
            string para= "";
    
            public static readonly BindableProperty UriProperty = BindableProperty.Create (
                propertyName: "Uri",
                returnType: typeof(string),
                declaringType: typeof(HybridWebView),
                defaultValue: default(string));
    
            public string Uri {
                get { return (string)GetValue (UriProperty); }
                set { SetValue (UriProperty, value); }
            }
    
            public void RegisterAction (Action<string> callback)
            {
                action = callback;
            }
    
            public void Cleanup ()
            {
                action = null;
            }
    
            public void InvokeAction (string data)
            {
                if (action == null || data == null) {
                    return;
                }
                action.Invoke(data);
            }
    
            public void setPara(string data)
            {
                para = data;
            }
            public string getPara()
            {
                return para;
            }
    
        }
    }
    

    As you have noticed, I add a variable "para" in the HybridWebview Class as well as the two methods "setPara()" and "getPara()". The key thing here is that, I find out that these two methods could be called both from the shared code and the rendered code.

    If you want to call the setPara in the shared code, what you need to do is:
    var hybridWebView = new HybridWebView
    {
    Uri = "google.com",
    HorizontalOptions = LayoutOptions.Center,
    VerticalOptions = LayoutOptions.FillAndExpand
    };
    hybridWebView.setPara("hello world");

    Then, you may want to call the getPara in the rendered code, what you need to do is (at least in iOS):
    string para= Element.getPara();
    Noticed that here Element is a reserved variable refer to the hybridWebveiw object under iOS. I believe there is a similar reserved variable under UWP.

    Hope that this could help you solve the problem!

  • KieranKellyKieranKelly IEMember

    for some reason reply will not post, so I put it in attached txt file, hope if not of use to you, then others :)

    Thanks for help

  • @KieranKelly said:
    for some reason reply will not post, so I put it in attached txt file, hope if not of use to you, then others :)

    Thanks for help

    Cool! Glad to know that it is getting work!

  • KieranKellyKieranKelly IEMember

    Hi,
    Thanks for the quick reply. I'm still kinda new to this, so please bear with me. I kinda thought that I would have to evaluate the script on the native platform, however If I add a method to the rendered view, I cannot see how I can execute it from the shared code. i.e. If I create a method in the definition of the shared hybridWebView, how do I execute it from the shared code? I have primarily been working with UWP first, however I have attached my IOS HybridWebViewRenderer code. I am not sure where your code would go, would you mind offering me some guidance

    using System.IO;
    using CustomRenderer;
    using CustomRenderer.iOS;
    using Foundation;
    using WebKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;

    [assembly: ExportRenderer (typeof(HybridWebView), typeof(HybridWebViewRenderer))]
    namespace CustomRenderer.iOS
    {
    public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
    {
    const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
    WKUserContentController userController;

        protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> 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, "InvokeCSharpMethod");
    
                var config = new WKWebViewConfiguration { UserContentController = userController };
                var webView = new WKWebView (Frame, config);
                SetNativeControl (webView);
            }
            if (e.OldElement != null) {
                userController.RemoveAllUserScripts ();
                userController.RemoveScriptMessageHandler ("InvokeCSharpMethod");
                var hybridWebView = e.OldElement as HybridWebView;
                hybridWebView.Cleanup ();
            }
            if (e.NewElement != null) {
                string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri));
                Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false)));
            }
        }
    
        public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message)
        {
            Element.InvokeCSharpMethod(message.Body.ToString ());
        }
    }
    

    }

    thanks

  • KieranKellyKieranKelly IEMember

    Hi,
    Thanks for the quick reply. I'm still kinda new to this, so please bear with me. I kinda thought that I would have to evaluate the script on the native platform, however If I add a method to the rendered view, I cannot see how I can execute it from the shared code. i.e. If I create a method in the definition of the shared hybridWebViewRenderer for each platform i.e. no HybridViewRenderer on shared project only in platform specific one, how do I execute it from the shared code?

    One other question, I have primarily been working with UWP first, however I have attached my IOS HybridWebViewRenderer code. I am not sure where your code would go, would you mind offering me some guidance

    using System.IO;
    using CustomRenderer;
    using CustomRenderer.iOS;
    using Foundation;
    using WebKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;

    [assembly: ExportRenderer (typeof(HybridWebView), typeof(HybridWebViewRenderer))]
    namespace CustomRenderer.iOS
    {
    public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
    {
    const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
    WKUserContentController userController;

        protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> 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, "InvokeCSharpMethod");
    
                var config = new WKWebViewConfiguration { UserContentController = userController };
                var webView = new WKWebView (Frame, config);
                SetNativeControl (webView);
            }
            if (e.OldElement != null) {
                userController.RemoveAllUserScripts ();
                userController.RemoveScriptMessageHandler ("InvokeCSharpMethod");
                var hybridWebView = e.OldElement as HybridWebView;
                hybridWebView.Cleanup ();
            }
            if (e.NewElement != null) {
                string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri));
                Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false)));
            }
        }
    
        public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message)
        {
            Element.InvokeCSharpMethod(message.Body.ToString ());
        }
    }
    

    }

    Thanks

  • KieranKellyKieranKelly IEMember

    Hi,
    Thanks for the quick reply. I'm still kinda new to this, so please bear with me. I kinda thought that I would have to evaluate the script on the native platform, however If I add a method to the rendered view, I cannot see how I can execute it from the shared code. i.e. If I create a method in the definition of the shared hybridWebViewRenderer i.e. no HybridViewRenderer on shared project only in platform specific one, how do I execute it from the shared code?

    One other questioI have primarily been working with UWP first, however I have attached my IOS HybridWebViewRenderer code. I am not sure where your code would go, would you mind offering me some guidance

    using System.IO;
    using CustomRenderer;
    using CustomRenderer.iOS;
    using Foundation;
    using WebKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;

    [assembly: ExportRenderer (typeof(HybridWebView), typeof(HybridWebViewRenderer))]
    namespace CustomRenderer.iOS
    {
    public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
    {
    const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
    WKUserContentController userController;

        protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> 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, "InvokeCSharpMethod");
    
                var config = new WKWebViewConfiguration { UserContentController = userController };
                var webView = new WKWebView (Frame, config);
                SetNativeControl (webView);
            }
            if (e.OldElement != null) {
                userController.RemoveAllUserScripts ();
                userController.RemoveScriptMessageHandler ("InvokeCSharpMethod");
                var hybridWebView = e.OldElement as HybridWebView;
                hybridWebView.Cleanup ();
            }
            if (e.NewElement != null) {
                string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri));
                Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false)));
            }
        }
    
        public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message)
        {
            Element.InvokeCSharpMethod(message.Body.ToString ());
        }
    }
    

    }

    Thanks

  • KieranKellyKieranKelly IEMember

    Hi,

    Thanks for the reply. Starting to get it to work, following for your info as your posts helped me, hopefully this may help others;

    What I was trying to do was execute the shared code on the native platform. I tried using a dependency service to do this, however the variable Control and Element were null. So what I did was create a static variable in the HybridViewRenderer on the native platform (so far only UWP and Android, UWP working just started Android and trying to get that to work) e.g.

    static WebView webView; (UWP) or static Android.Webkit.WebView webView; (android)

    then

    (uwp)

    static WebView webView; (UWP)

    public async Task ExecuteJavascriptFunction(string javascriptFunction, string base64Photo)
    {
    string[] parmArray = new string[1] { base64Photo };
    await webView.InvokeScriptAsync(javascriptFunction, parmArray);
    }

    protected override void OnElementChanged(ElementChangedEventArgs e)
    {
    base.OnElementChanged(e);

            if (Control == null) 
            {
                SetNativeControl(new Windows.UI.Xaml.Controls.WebView());
                webView = Control;
    

    .....

    Also for UWP read somewhere I must have following in App.xaml.cs, don't know if needed in Andriod (I may try and remove from UWP and see if it works)

    public App()
    {
    // register the dependencies in the same
    Xamarin.Forms.DependencyService.Register();
    Xamarin.Forms.DependencyService.Register();
    this.InitializeComponent();

    ....

    (android)

    static Android.Webkit.WebView webView; (android)

    public async Task ExecuteJavascriptFunction(string javascriptFunction, string base64Photo)
    {
    javascriptFunction = javascriptFunction + "(" + base64Photo + ")";
    webView.EvaluateJavascript(javascriptFunction, null);
    }

    protected override void OnElementChanged (ElementChangedEventArgs e)
    {
    base.OnElementChanged (e);

            if (Control == null) {
                webView = new Android.Webkit.WebView(Forms.Context);
    

    ........

    Then from the shared code

    await DependencyService.Get().ExecuteJavascriptFunction("funPhotoLoad", base64Photo);

    One thing to watch out for is the way you pass the function name and parameters that is to be executed, its different in UWP and Android, since I haven't got to IOS, I cannot offer and info on that.

    Like I say, just started Android and all I can say is that its not running at moment.

  • KieranKellyKieranKelly IEMember

    Hi,

    Thanks for the reply. Starting to get it to work

    What I was trying to do was execute the shared code on the native platform. I tried using a dependency service to do this, however the variable Control and Element were null. So what I did was create a static variable in the HybridViewRenderer on the native platform (so far only UWP and Android, UWP working just started Android and trying to get that to work) e.g.

    static WebView webView; (UWP) or static Android.Webkit.WebView webView; (android)

    then

    (uwp)

    static WebView webView; (UWP)

    public async Task ExecuteJavascriptFunction(string javascriptFunction, string base64Photo)
    {
    string[] parmArray = new string[1] { base64Photo };
    await webView.InvokeScriptAsync(javascriptFunction, parmArray);
    }

    protected override void OnElementChanged(ElementChangedEventArgs e)
    base.OnElementChanged(e);

            if (Control == null) 
                SetNativeControl(new Windows.UI.Xaml.Controls.WebView());
                webView = Control;
    

    .....

    Also for UWP read somewhere I must have following in App.xaml.cs, don't know if needed in Andriod (I may try and remove from UWP and see if it works)

    public App()

            // register the dependencies in the same
            Xamarin.Forms.DependencyService.Register<TakePhoto>();
            Xamarin.Forms.DependencyService.Register<HybridWebViewRenderer>();
            this.InitializeComponent();
    

    ....

    (android)

    static Android.Webkit.WebView webView; (android)

    public async Task ExecuteJavascriptFunction(string javascriptFunction, string base64Photo)
    {
    javascriptFunction = javascriptFunction + "(" + base64Photo + ")";
    webView.EvaluateJavascript(javascriptFunction, null);
    }

    protected override void OnElementChanged (ElementChangedEventArgs e)

            base.OnElementChanged (e);
    
            if (Control == null) 
                webView = new Android.Webkit.WebView(Forms.Context);
    

    ........

    Then from the shared code;

    await DependencyService.Get().ExecuteJavascriptFunction("funPhotoLoad", base64Photo);

    One thing to watch out for is the way you pass the function name and parameters that is to be executed, its different in UWP and Android, since I haven't got to IOS, I cannot offer and info on that.

    Like I said, just started Android and all I can say is that its not running at moment.

  • KieranKellyKieranKelly IEMember

    Hi,
    Thanks for the quick reply. I'm still kinda new to this, so please bear with me. I kinda thought that I would have to evaluate the script on the native platform, however If I add a method to the rendered view, I cannot see how I can execute it from the shared code. i.e. If I create a method in the definition of the shared hybridWebViewRenderer for each platform i.e. no HybridViewRenderer on shared project only in platform specific one, how do I execute it from the shared code?

    One other question, I have primarily been working with UWP first, however I have attached my IOS HybridWebViewRenderer code. I am not sure where your code would go, would you mind offering me some guidance

    using System.IO;
    using CustomRenderer;
    using CustomRenderer.iOS;
    using Foundation;
    using WebKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;

    [assembly: ExportRenderer (typeof(HybridWebView), typeof(HybridWebViewRenderer))]
    namespace CustomRenderer.iOS
    {
    public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
    {
    const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
    WKUserContentController userController;

        protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> 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, "InvokeCSharpMethod");
    
                var config = new WKWebViewConfiguration { UserContentController = userController };
                var webView = new WKWebView (Frame, config);
                SetNativeControl (webView);
            }
            if (e.OldElement != null) {
                userController.RemoveAllUserScripts ();
                userController.RemoveScriptMessageHandler ("InvokeCSharpMethod");
                var hybridWebView = e.OldElement as HybridWebView;
                hybridWebView.Cleanup ();
            }
            if (e.NewElement != null) {
                string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri));
                Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false)));
            }
        }
    
        public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message)
        {
            Element.InvokeCSharpMethod(message.Body.ToString ());
        }
    }
    

    }

    Thanks

  • KieranKellyKieranKelly IEMember

    Hi,

    Thanks for the reply. Starting to get it to work, following for your info as your posts helped me, hopefully this may help others;

    What I was trying to do was execute the shared code on the native platform. I tried using a dependency service to do this, however the variable Control and Element were null. So what I did was create a static variable in the HybridViewRenderer on the native platform (so far only UWP and Android, UWP working just started Android and trying to get that to work) e.g.

    static WebView webView; (UWP) or static Android.Webkit.WebView webView; (android)

    then

    (uwp)

    static WebView webView; (UWP)

    public async Task ExecuteJavascriptFunction(string javascriptFunction, string base64Photo)
    {
    string[] parmArray = new string[1] { base64Photo };
    await webView.InvokeScriptAsync(javascriptFunction, parmArray);
    }

    protected override void OnElementChanged(ElementChangedEventArgs e)
    {
    base.OnElementChanged(e);

            if (Control == null) 
            {
                SetNativeControl(new Windows.UI.Xaml.Controls.WebView());
                webView = Control;
    

    .....

    Also for UWP read somewhere I must have following in App.xaml.cs, don't know if needed in Andriod (I may try and remove from UWP and see if it works)

    public App()
    {
    // register the dependencies in the same
    Xamarin.Forms.DependencyService.Register();
    Xamarin.Forms.DependencyService.Register();
    this.InitializeComponent();

    ....

    (android)

    static Android.Webkit.WebView webView; (android)

    public async Task ExecuteJavascriptFunction(string javascriptFunction, string base64Photo)
    {
    javascriptFunction = javascriptFunction + "(" + base64Photo + ")";
    webView.EvaluateJavascript(javascriptFunction, null);
    }

    protected override void OnElementChanged (ElementChangedEventArgs e)
    {
    base.OnElementChanged (e);

            if (Control == null) {
                webView = new Android.Webkit.WebView(Forms.Context);
    

    ........

    Then from the shared code

    await DependencyService.Get().ExecuteJavascriptFunction("funPhotoLoad", base64Photo);

    One thing to watch out for is the way you pass the function name and parameters that is to be executed, its different in UWP and Android, since I haven't got to IOS, I cannot offer and info on that.

    Like I say, just started Android and all I can say is that its not running at moment.

Sign In or Register to comment.