Xamarin.iOS 无法从 WebView Navigating 和 Navigated 事件控制 iOS 活动指示器

问题描述

我有一个包含 WebView 和 ActivityIndi​​cator 的 ContentPage。有用于 Navigating 和 Navigated 事件的事件处理程序来激活和取消激活 ActivityIndi​​cator。在 iOS 上,会触发 Navigating 事件,但不会始终触发 Navigated 事件。这通常会使 ActivityIndi​​cator 处于永久激活状态。这可以通过导航到 https://www.fairfaxcounty.gov/topics/contact-us 来重现。

有人建议我使用自定义 Web 渲染器来响应 DidFinishNavigation 和 DidFailNavigation 并发送 MessagingCenter 消息以关闭活动指示器。我添加了这个并且它(大部分)有效,但无论出于何种原因(可能是更新到 XF 5 或在幕后更改为 WKWebView),它已停止工作。 DidFinishNavigation 仍然在自定义渲染器中触发,但现在 Navigating 和 Navigated 不再在 WebView 上触发。 DidFailNavigation 永远不会触发,页面呈现完全正确,所以我不认为导航失败是问题所在。如果我移除自定义渲染器,它会返回到 Navigating 事件触发状态,并且 Navigated 仅在某些时候触发。

如果您对此有任何见解,我将不胜感激。

<ContentPage xmlns = "http://xamarin.com/schemas/2014/forms"
                xmlns: x = "http://schemas.microsoft.com/winfx/2009/xaml"
                xmlns: vm = "clr-namespace:FFXPubXam.viewmodels"
                x: Class = "FFXPubXam.Views.WebPage"
                x: Name = "root"
                Shell.NavBarIsVisible = "false" >
    < ContentPage.Content >
        < Grid >
            < WebView x: Name = "WebViewControl"
                        Source = "{Binding Url}"
                        Navigating = "WebViewControl_Navigating"
                        Navigated = "WebViewControl_Navigated" />
            < ActivityIndicator x: Name = "activity"
                                IsRunning = "False"
                                IsEnabled = "False"
                                IsVisible = "False"
                                HeightRequest = "40"
                                WidthRequest = "100"
                                VerticalOptions = "CenterandExpand"
                                HorizontalOptions = "CenterandExpand"
                                Color = "{DynamicResource FFX_Blue4}"
                                BackgroundColor = "Transparent”/>
            </ Grid >
    </ ContentPage.Content >
</ ContentPage >

[QueryProperty("Url","url")]
[XamlCompilation(XamlCompilationoptions.Compile)]
public partial class WebPage : ContentPage
{
    private string baseUrl = string.Empty;
    private bool subscribed = false;

    private string url;

    public string Url
    {
        set
        {
            if (value != null)
            {
                url = Uri.UnescapeDataString(value);
            }
        }
        get
        {
            return url;
        }
    }

    public WebPage()
    {
        InitializeComponent();
        BindingContext = new WebPageviewmodel();
    }

    public WebPage(string arg)
    {
        Url = arg;

        InitializeComponent();
        BindingContext = new WebPageviewmodel();
    }

    protected override void OnAppearing()
    {
        if (baseUrl == string.Empty)
        {
            if (Url != null)
            {
                baseUrl = Url;
            }
        }

        if (WebViewControl.source?.ToString() != baseUrl)
        {
            WebViewControl.source = baseUrl;
        }

        base.OnAppearing();

        Logging.Write(LogType.Info,$"OnAppearing: Url={url}");

        if (!subscribed) SubScribe();

        ToggleActivityIndicator(true);
    }

    private void WebViewControl_Navigating(object sender,WebNavigatingEventArgs e)
    {
        Logging.Write(LogType.Info,$"Navigating: Url={url}");
        ToggleActivityIndicator(true);
    }

    private void WebViewControl_Navigated(object sender,WebNavigatedEventArgs e)
    {
        Logging.Write(LogType.Info,$"Navigated: Url={url}");
        ToggleActivityIndicator(false);
    }

    private void ToggleActivityIndicator(bool state)
    {
        Logging.Write(LogType.Info,$"ToggleActivityIndicator: state={state} stack={new StackTrace()}");
        activity.IsRunning = state;
        activity.IsEnabled = state;
        activity.IsVisible = state;
    }

    private void SubScribe()
    {
        Logging.Write(LogType.Info,$"Subscribe: call stack={new StackTrace()}");

        MessagingCenter.Subscribe<object>(this,"End",(sender) =>
        {
            ToggleActivityIndicator(false);
        });
    }
}
    
[assembly: ExportRenderer(typeof(WebView),typeof(CustomWebVieWrenderer))]
namespace FFXPubXam.iOS.Renderers
{
    class MyDelegate : WKNavigationDelegate
    {
        public override void DidFinishNavigation(WKWebView webView,WKNavigation navigation)
        {
            MessagingCenter.Send<object>(this,"End");
            Logging.Write(LogType.Info,$"DidFinishNavigation: after Send");
        }
        public override void DidFailNavigation(WKWebView webView,WKNavigation navigation,NSError error)
        {
            MessagingCenter.Send<object,string>(this,error.ToString());
        }
    }

    class CustomWebVieWrenderer : WkWebVieWrenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);
            if (e.NewElement != null)
            {
                this.NavigationDelegate = new MyDelegate();
            }
        }
    }
}

解决方法

我测试了您在 XF5 下提供的 URL。即使花了很长时间,WebViewControl_Navigated 还是被触发了。我还测试了网址“https://stackoverflow.com/”,WebViewControl_Navigated 响应很快。

恐怕这个问题的根本原因是“fairfaxcounty”网站。

此外,如果您想在自定义渲染器中调用“ToggleActivityIndi​​cator”,MessagingCenter 不是一个好的选择。您可以通过事件更有效地实现它。

在您的 CustomWebView 类中定义事件:

public class CustomWebView : WebView
{
    public delegate void NavigateDel(bool flag);

    public event NavigateDel NavigateEvent;

    public void CallNavigate(bool flag)
    {
        NavigateEvent(flag);
    }
}

然后在“WebPage”订阅事件:

public WebPage()
{
    InitializeComponent();
    BindingContext = new WebPageViewModel();
    WebViewControl.NavigateEvent += ToggleActivityIndicator;
}

如果您已经创建了自定义渲染器,则无需订阅 Navigating 和 Navigated。要在页面加载时显示指示器,您还需要覆盖“DidStartProvisionalNavigation”。

class MyDelegate : WKNavigationDelegate
{
    CustomWebView customView;

    public MyDelegate(CustomWebView view)
    {
        customView = view;
    }

    public override void DidStartProvisionalNavigation(WKWebView webView,WKNavigation navigation)
    {
        customView.CallNavigate(true);
    }

    public override void DidFinishNavigation(WKWebView webView,WKNavigation navigation)
    {
        customView.CallNavigate(false);
    }
    public override void DidFailNavigation(WKWebView webView,WKNavigation navigation,NSError error)
    {
    }
}

class CustomWebViewRenderer : WkWebViewRenderer
{
    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            CustomWebView control = (CustomWebView)Element;
            this.NavigationDelegate = new MyDelegate(control);
        }
    }
}