Xamarin.iOS 无法控制 iOS Activity 来自 WebView 导航和导航事件的指示器

Xamarin.iOS Can’t control iOS Activity Indicator from WebView Navigating and Navigated events

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

来重现

有人建议我使用自定义网络渲染器来响应 DidFinishNavigation 和 DidFailNavigation 并发送 MessagingCenter 消息以关闭 activity 指示器。我添加了这个并且它(大部分)工作了,但是无论出于什么原因(可能是对 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, "End", 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。我还测试了url“https://whosebug.com/”,WebViewControl_Navigated响应很快。

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

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

在您的 CustomWebView 中定义事件 class:

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

    public event NavigateDel NavigateEvent;

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

然后在“网页”订阅活动:

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