Xamarin HybridWebView 在 EvaluateJavaScript 中抛出异常

Xamarin HybridWebView throwing exception at EvaluateJavaScript

我昨天花了几个小时找出为什么我无法访问我的混合 wkWebView 的(显然有效的)参数。 在正确触发的 DidFinishNavigation 覆盖下的 iOS 部分,我在尝试 EvaluateJavaScriptAsync 甚至访问 Source 参数时总是遇到 NullObjectReference 异常。

然后我终于发现我看错了对象:我正在访问 Forms HybridWebView 对象(在 "chain" 中向上)而不是 iOS webview 对象(在 "chain" 中向下)渲染器)。在下面的代码中,DidFinishNavingation 中的 "webView" 参数代替了 webViewRenderer.Element 中的 _wkWebView(参见最后的代码部分)

现在我的代码可以工作了,但我仍然想知道我的平台特定集成的总体概念是否正确,或者表单 hybridwebview 中可用的表单函数(例如 EvaluateJavaScript)是否应该直接工作?我是否遗漏了一些 link 来将它们与底层 iOS 网络视图连接起来?

我的 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));
    }
}

和渲染器的连接 iOS 部分:

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

    }

最后是 iOS 中的 DidFinishLoading 事件处理程序 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);
        }


    }

}

当我们调用行时

SetNativeControl(_wkWebView);

您在Forms中定义的WebView又被初始化了。所以你需要在 Custom Renderer 中实现大部分功能。特别是在iOS的WebView中,如果你想在它的生命周期中添加监听器或者调用一些JS方法,在Custom Renderer中实现是最好的方法(或者说唯一的方法) ) 。