Kerry
Kerry

Reputation: 161

Xamarin HybridWebView throwing exception at EvaluateJavaScript

I spent some hours yesterday finding out why I couldn't access the (obviously working) params of my Hybrid wkWebView. In the iOS part under the correctly fired DidFinishNavigation override, I always got a NullObjectReference Exception when trying to EvaluateJavaScriptAsync or even access the Source parameter.

Then I finally found out that I was looking at the wrong object: I was accessing the Forms HybridWebView object (upwards in the "chain") instead of the iOS webview object (downwards from the renderer). In the code below the "webView" parameter from DidFinishNavingation istead of the _wkWebView from the webViewRenderer.Element (see last code part)

Now my code is working, but I'm still wondering whether my overall concept of the platform specific intergration is correct or whether the Forms functions available in the forms hybridwebview (such as EvaluateJavaScript) should be working directly? Am I'm missing some link to connect those with the underlying iOS webview?

My shared Forms code for the HybidWebView:

public class MyWebView : WebView
{
    public event EventHandler SwipeLeft;
    public event EventHandler SwipeRight;

    public void OnSwipeLeft() =>
        SwipeLeft?.Invoke(this, null);

    public void OnSwipeRight() =>
        SwipeRight?.Invoke(this, null);

    public static readonly BindableProperty UrlProperty = BindableProperty.Create(
        propertyName: "Url",
        returnType: typeof(string),
        declaringType: typeof(MyWebView),
        defaultValue: default(string));

    public string Url
    {
        get { return (string)GetValue(UrlProperty); }
        set
        {
            SetValue(UrlProperty, value);
            NotifyPropertyChanged(nameof(HTML));
        }
    }

    public static readonly BindableProperty HTMLProperty = BindableProperty.Create(
        propertyName: "HTML",
        returnType: typeof(string),
        declaringType: typeof(MyWebView),
        defaultValue: default(string));

    public string HTML
    {
        get { return (string)GetValue(HTMLProperty); }
        set
        {
            SetValue(HTMLProperty, value);
            NotifyPropertyChanged(nameof(HTML));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

and the connected iOS part for the Renderer:

    public class MyWebViewRenderer : ViewRenderer<MyWebView, WKWebView>
{
    WKWebView _wkWebView;

    protected override void OnElementChanged(ElementChangedEventArgs<MyWebView> e)
    {
        System.Console.WriteLine("--- Loading wkWebView ---");
        base.OnElementChanged(e);

        if (Control == null)
        {
            Console.WriteLine("+++ WKW: Control null -> init");

            var config = new WKWebViewConfiguration();
            _wkWebView = new WKWebView(Frame, config);

            SetNativeControl(_wkWebView);
            _wkWebView.NavigationDelegate = new ExtendedWKWebViewDelegate(this);

        }

        if (e.NewElement != null)
        {
            Console.WriteLine("+++ WKW: Control exists -> init GestureRecognizers");

            UISwipeGestureRecognizer leftgestureRecognizer = new UISwipeGestureRecognizer(this, new Selector("SwipeEvent:"));

            leftgestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;

            UISwipeGestureRecognizer rightgestureRecognizer = new UISwipeGestureRecognizer(this, new Selector("SwipeEvent:"));
            rightgestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Right;

            leftgestureRecognizer.Delegate = new MyWebViewDelegate();
            rightgestureRecognizer.Delegate = new MyWebViewDelegate();

            this.AddGestureRecognizer(leftgestureRecognizer);
            this.AddGestureRecognizer(rightgestureRecognizer);
        }

        NSUrl baseURL = new NSUrl(App.dirNews, true);
        string viewFile = Path.Combine(App.dirNews, "view.html");
        NSUrl fileURL = new NSUrl(viewFile, false);

        if (Element?.HTML != null && Element?.HTML != "")
        {
            System.Console.WriteLine("--- Showing HTTP content ---");
            File.WriteAllText(viewFile, Element.HTML, System.Text.Encoding.UTF8);
            Control.LoadFileUrl(fileURL, baseURL);
        }
        else if (Element?.Url != null && Element?.Url != "")
        {
            System.Console.WriteLine("--- Loading Web page ---");
            System.Console.WriteLine("--- " + Element.Url + " ---");
            NSUrlRequest myRequest = new NSUrlRequest(new NSUrl(Element.Url), NSUrlRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData, 120);
            Control.LoadRequest(myRequest);
        }

    }

and finally the DidFinishLoading event handler in iOS class:

    class ExtendedWKWebViewDelegate : WKNavigationDelegate
{

    MyWebViewRenderer webViewRenderer;

    public ExtendedWKWebViewDelegate(MyWebViewRenderer _webViewRenderer = null)
    {
        webViewRenderer = _webViewRenderer ?? new MyWebViewRenderer();
    }

    [Export("webView:didFinishNavigation:")]
    public override async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
    {
        try
        {
            var _webView = webViewRenderer.Element as WebView;
            if (_webView != null)
            {
                await System.Threading.Tasks.Task.Delay(100); // wait here till content is rendered
                _webView.HeightRequest = (double)webView.ScrollView.ContentSize.Height;

                //### get html from site
                var myRes = await webView.EvaluateJavaScriptAsync("document.body.innerHTML");

                //### Flag complete status
                MessagingCenter.Send<object, string>(this, "didFinishNavigation", myRes.ToString());

            }
        }

        catch (Exception ex)
        {
            //Log the Exception
            Console.WriteLine("*** CustomWebView Exception: " + ex.Message + "/" + ex.InnerException);
        }


    }

}

Upvotes: 0

Views: 321

Answers (1)

Lucas Zhang
Lucas Zhang

Reputation: 18861

When we invoked the line

SetNativeControl(_wkWebView);

The WebView that you define in Forms been initialized again . So you need to implement most of function in Custom Renderer . Especially in WebView of iOS, if you want to add listener the lifecycle of it or invoke some JS method , implement it in Custom Renderer is the best way (or the only way) .

Upvotes: 1

Related Questions