Xamarin.iOS 无法控制 iOS Activity 来自 WebView 导航和导航事件的指示器
Xamarin.iOS Can’t control iOS Activity Indicator from WebView Navigating and Navigated events
我有一个包含 WebView 和 ActivityIndicator 的 ContentPage。 Navigating 和 Navigated 事件有事件处理程序来激活和取消激活 ActivityIndicator。在 iOS,Navigating 事件触发,但 Navigated 事件未持续触发。这通常会使 ActivityIndicator 处于永久激活状态。这可以通过导航到 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”。
此外,如果您想在自定义渲染器中调用“ToggleActivityIndicator”,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);
}
}
}
我有一个包含 WebView 和 ActivityIndicator 的 ContentPage。 Navigating 和 Navigated 事件有事件处理程序来激活和取消激活 ActivityIndicator。在 iOS,Navigating 事件触发,但 Navigated 事件未持续触发。这通常会使 ActivityIndicator 处于永久激活状态。这可以通过导航到 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”。
此外,如果您想在自定义渲染器中调用“ToggleActivityIndicator”,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);
}
}
}