WebView 和错误管理,怎么了?
WebView and error management, what's wrong?
我正在开发 Xamarin.Forms 应用程序,我需要在其中集成 WebView
以通过外部 管理预订 URL.
所以在我看来基本上我已经这样做了:
<WebView x:Name="webView" Source="{Binding BookingUrl}"
WidthRequest="1000" HeightRequest="1000">
我想解决用户在打开此页面时可能遇到的一些错误:无法访问互联网、超时、服务器不可用……
为此,我使用 EventToCommandBehavior
访问 ViewModel 中的事件 Navigating
和 Navigating
。
所以我的 XAML 看起来像这样:
<WebView x:Name="webView" Source="{Binding AvilaUrlBooking}"
WidthRequest="1000" HeightRequest="1000">
<WebView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="Navigating"
Command="{Binding NavigatingCommand}" />
<behaviors:EventToCommandBehavior
EventName="Navigated"
Command="{Binding NavigatedCommand}" />
</WebView.Behaviors>
</WebView>
而 ViewModel 是这样的:
public ICommand NavigatingCommand
{
get
{
return new Xamarin.Forms.Command<WebNavigatingEventArgs>(async (x) =>
{
if (x != null)
{
await WebViewNavigatingAsync(x);
}
});
}
}
private Task WebViewNavigatingAsync(WebNavigatingEventArgs eventArgs)
{
if (!IsConnected)
ServiceErrorKind = ServiceErrorKind.NoInternetAccess;
IsBusy = true;
return Task.CompletedTask;
}
public ICommand NavigatedCommand
{
get
{
return new Xamarin.Forms.Command<WebNavigatedEventArgs>(async (x) =>
{
if (x != null)
{
await WebViewNavigatedAsync(x);
}
});
}
}
private Task WebViewNavigatedAsync(WebNavigatedEventArgs eventArgs)
{
IsBusy = false;
IsFirstDisplay = false;
switch (eventArgs.Result)
{
case WebNavigationResult.Cancel:
// TODO - do stuff here
break;
case WebNavigationResult.Failure:
// TODO - do stuff here
break;
case WebNavigationResult.Success:
// TODO - do stuff here
break;
case WebNavigationResult.Timeout:
// TODO - do stuff here
break;
default:
// TODO - do stuff here
break;
}
return Task.CompletedTask;
}
bool isFirstDisplay;
public bool IsFirstDisplay
{
get { return isFirstDisplay; }
set { SetProperty(ref isFirstDisplay, value); }
}
public BookingViewModel()
{
_eventTracker = new AppCenterEventTracker();
IsFirstDisplay = true;
Title = "Booking";
IsConnected = Connectivity.NetworkAccess == NetworkAccess.Internet;
Connectivity.ConnectivityChanged += OnConnectivityChanged;
}
如果我使用右 URL,在 iOS 和 Android 上一切正常。
但是,如果我使用“错误的”URL(例如缺少字符),这仅适用于 Android:这种情况WebNavigationResult.Failure
在WebViewNavigatedAsync()
中被捕获,但我在iOS.
中没有进入WebViewNavigatedAsync()
=>这正常吗?
我实现了一个“刷新”按钮来管理“无法访问互联网”错误。这个按钮可以通过 ToolBarItem
访问,在 ViewModel 中是这样的:
public void Refresh(object sender)
{
try
{
var view = sender as Xamarin.Forms.WebView;
view.Reload();
}
catch (Exception ex)
{
}
}
但在这些情况下,我在激活飞行模式后也有两种不同的行为:
- 在 iOS,当没有互联网接入时:我不进入
WebViewNavigatedAsync()
,即使互联网接入再次可用并且我点击 “刷新”按钮,我只路过 WebViewNavigatingAsync()
- 在Android,当没有互联网接入时:我输入
WebViewNavigatedAsync()
,当互联网再次可用时,我点击在 “刷新”按钮 上,我通过了 WebViewNavigatingAsync()
和 WebViewNavigatedAsync()
=> 这正常吗?有没有合适的方法来管理这个?
is this normal?
根据我的测试。是的,我得到了相同的结果,如果我们输入错误 url,webview 始终是 white-empty 在 iOS 中的视图。所以 NavigatedCommand
无法执行。
如果我们使用正确的 url,webview 可以执行 NavigatedCommand
,并且 运行 结果如下图所示。
Is there a proper way to manager this?
我们可以在 iOS 中为 webview 使用自定义渲染器来处理这种情况。
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace MyCarsourlView.iOS
{
[Obsolete]
class CustomWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement == null) { Delegate = new CustomWebViewDelegate(); }
}
}
}
internal class CustomWebViewDelegate : UIWebViewDelegate
{
#region Event Handlers
public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType)
{
//Could add stuff here to redirect the user before the page loads, if you wanted to redirect the user you would do the redirection and then return false
return true;
}
public override void LoadFailed(UIWebView webView, NSError error)
{
Console.WriteLine("\nIn AppName.iOS.CustomWebViewRenderer - Error: {0}\n", error.ToString()); //TODO: Do something more useful here
//Here, you can test to see what kind of error you are getting and act accordingly, either by showing an alert or rendering your own custom error page using basic HTML
}
public override void LoadingFinished(UIWebView webView)
{ //You could do stuff here such as collection cookies form the WebView }
#endregion
}
}
如果我输入错误url,LoadFailed
会被执行
我发现了另一种似乎有效的方法,基于以下链接:
它可能并不完美,尤其是当我需要从 ViewModel 访问所需的事件时。
所以我创建了一个继承自 WebView
的 CustomWebView
控件:
public class CustomWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(CustomWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public CustomWebViewErrorKind ErrorKind { get; set; }
public event EventHandler LoadingStart;
public event EventHandler LoadingFinished;
public event EventHandler LoadingFailed;
/// <summary>
/// The event handler for refreshing the page
/// </summary>
public EventHandler OnRefresh { get; set; }
public void InvokeCompleted()
{
if (this.LoadingFinished != null)
{
ErrorKind = WebViewErrorKind.None;
this.LoadingFinished.Invoke(this, null);
}
}
public void InvokeStarted()
{
if (this.LoadingStart != null)
{
ErrorKind = WebViewErrorKind.None;
this.LoadingStart.Invoke(this, null);
}
}
public void InvokeFailed(CustomWebViewErrorKind errorKind)
{
if (this.LoadingFailed != null)
{
ErrorKind = errorKind;
this.LoadingFailed.Invoke(this, null);
}
}
/// <summary>
/// Refreshes the current page
/// </summary>
public void Refresh()
{
OnRefresh?.Invoke(this, new EventArgs());
}
}
然后我有自定义 CustomWebView
:
行为的 CustomWkWebViewRenderer
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWkWebViewRenderer))]
namespace MyProject.iOS.Renderers
{
public class CustomWkWebViewRenderer : ViewRenderer<CustomWebView, WKWebView>
{
public CustomWkWebViewRenderer()
{
Debug.WriteLine($"CustomWkWebViewRenderer - Ctor");
}
WKWebView webView;
protected override void OnElementChanged(ElementChangedEventArgs<CustomWebView> e)
{
base.OnElementChanged(e);
Debug.WriteLine($"CustomWkWebViewRenderer - OnElementChanged()");
if (Control == null)
{
Debug.WriteLine($"CustomWkWebViewRenderer - OnElementChanged() - Control == null");
webView = new WKWebView(Frame, new WKWebViewConfiguration()
{
MediaPlaybackRequiresUserAction = false
});
webView.NavigationDelegate = new DisplayWebViewDelegate(Element);
SetNativeControl(webView);
Element.OnRefresh += (sender, ea) => Refresh(sender);
}
if (e.NewElement != null)
{
Debug.WriteLine($"CustomWkWebViewRenderer - OnElementChanged() - e.NewElement != null");
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
webView.NavigationDelegate = new DisplayWebViewDelegate(Element);
SetNativeControl(webView);
}
}
private void Refresh(object sender)
{
Debug.WriteLine($"CustomWkWebViewRenderer - Refresh()");
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
}
}
}
我还有 CustomWkWebViewNavigationDelegate
实现了这个渲染器的 WKNavigationDelegate
:
public class CustomWkWebViewNavigationDelegate : WKNavigationDelegate
{
private CustomWebView element;
public CustomWkWebViewNavigationDelegate(CustomWebView element)
{
Debug.WriteLine($"CustomWkWebViewNavigationDelegate - Ctor");
this.element = element;
}
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
Debug.WriteLine($"CustomWkWebViewNavigationDelegate - DidFinishNavigation");
element.InvokeCompleted();
//base.DidFinishNavigation(webView, navigation);
}
public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation)
{
Debug.WriteLine($"CustomWkWebViewNavigationDelegate - DidStartProvisionalNavigation");
element.InvokeStarted();
//base.DidStartProvisionalNavigation(webView, navigation);
}
[Export("webView:didFailProvisionalNavigation:withError:")]
public override void DidFailProvisionalNavigation(WKWebView webView, WKNavigation navigation, NSError error)
{
Debug.WriteLine($"CustomWkWebViewNavigationDelegate - DidFailProvisionalNavigation - error : {error}");
var errorKind = CustomWebViewErrorKind.None;
switch (error.Code)
{
case -1009: // no internet access
{
errorKind = CustomWebViewErrorKind.NoInternetAccess;
break;
}
case -1001: // timeout
{
errorKind = CustomWebViewErrorKind.Timeout;
break;
}
case -1003: // server cannot be found
case -1100: // url not found on server
default:
{
errorKind = CustomWebViewErrorKind.Failure;
break;
}
}
element.InvokeFailed(errorKind);
//base.DidFailProvisionalNavigation(webView, navigation, error);
}
}
有一个 CustomWebViewErrorKind
枚举可以让我在 ViewModel 中实现一个常见的错误管理:
public enum CustomWebViewErrorKind
{
None = 0,
NoInternetAccess = 1,
Failure = 2,
Timeout = 3,
Cancel = 8,
Other = 9
}
为了从 ViewModel 访问事件,我使用 EventToCommandBehavior
描述 there
所以,我已经公开了视图中的所有命令,如下所示:
<controls:CustomWebView x:Name="webView"
Source="{Binding MyUrlBooking}"
Uri="{Binding MyUrlBooking}"
WidthRequest="1000" HeightRequest="1000">
<WebView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="Navigating"
Command="{Binding NavigatingCommand}" />
<behaviors:EventToCommandBehavior
EventName="Navigated"
Command="{Binding NavigatedCommand}" />
<behaviors:EventToCommandBehavior
EventName="LoadingStart"
Command="{Binding LoadingStartCommand}" />
<behaviors:EventToCommandBehavior
EventName="LoadingFinished"
Command="{Binding LoadingFinishedCommand}" />
<behaviors:EventToCommandBehavior
EventName="LoadingFailed"
Command="{Binding LoadingFailedCommand}"
CommandParameter="{x:Reference webView}"
/>
</WebView.Behaviors>
</controls:CustomWebView>
最后,在我的 ViewModel 中,我为 Android 部分执行此操作:
public ICommand NavigatingCommand
{
get
{
return new Xamarin.Forms.Command<WebNavigatingEventArgs>(async (x) =>
{
if (x != null)
{
await WebViewNavigatingAsync(x);
}
});
}
}
private Task WebViewNavigatingAsync(WebNavigatingEventArgs eventArgs)
{
Debug.WriteLine($"BookingViewModel - WebViewNavigatingAsync()");
IsBusy = true;
return Task.CompletedTask;
}
public ICommand NavigatedCommand
{
get
{
return new Xamarin.Forms.Command<WebNavigatedEventArgs>(async (x) =>
{
if (x != null)
{
await WebViewNavigatedAsync(x);
}
});
}
}
private Task WebViewNavigatedAsync(WebNavigatedEventArgs eventArgs)
{
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync()");
IsBusy = false;
switch (eventArgs.Result)
{
case WebNavigationResult.Cancel:
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Cancel");
ErrorKind = CustomWebViewErrorKind.Cancel;
break;
case WebNavigationResult.Failure:
default:
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Failure");
IsConnected = Connectivity.NetworkAccess == NetworkAccess.Internet;
if (IsConnected)
{
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Failure : Failure");
ErrorKind = CustomWebViewErrorKind.Failure;
}
else
if (IsConnected)
{
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Failure : NoInternetAccess");
ErrorKind = CustomWebViewErrorKind.NoInternetAccess;
}
break;
case WebNavigationResult.Success:
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Success");
ErrorKind = CustomWebViewErrorKind.None;
IsFirstDisplay = false;
break;
case WebNavigationResult.Timeout:
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Timeout");
ErrorKind = CustomWebViewErrorKind.Timeout;
break;
}
return Task.CompletedTask;
}
我为 iOS 部分这样做:
public ICommand LoadingStartCommand
{
get
{
return new Xamarin.Forms.Command(async () =>
{
await WebViewLoadingStartAsync();
});
}
}
private Task WebViewLoadingStartAsync()
{
Debug.WriteLine($"BookingViewModel - WebViewLoadingStartAsync()");
IsBusy = true;
return Task.CompletedTask;
}
public ICommand LoadingFinishedCommand
{
get
{
return new Xamarin.Forms.Command(async () =>
{
await WebViewLoadingFinishedAsync();
});
}
}
private Task WebViewLoadingFinishedAsync()
{
Debug.WriteLine($"BookingViewModel - WebViewLoadingFinishedAsync()");
IsBusy = false;
return Task.CompletedTask;
}
public ICommand LoadingFailedCommand
{
get
{
return new Xamarin.Forms.Command<object>(async (object sender) =>
{
if (sender != null)
{
await WebViewLoadingFailedAsync(sender);
}
});
}
}
private Task WebViewLoadingFailedAsync(object sender)
{
Debug.WriteLine($"BookingViewModel - WebViewLoadingFailedAsync()");
var view = sender as CustomWebView;
var error = view.ErrorKind;
Debug.WriteLine($"BookingViewModel - WebViewLoadingFailedAsync() - error : {error}");
IsBusy = false;
return Task.CompletedTask;
}
像这样我能够管理错误,从 ViewModel 重试和刷新,即使它可能不是更好的解决方案...
我正在开发 Xamarin.Forms 应用程序,我需要在其中集成 WebView
以通过外部 管理预订 URL.
所以在我看来基本上我已经这样做了:
<WebView x:Name="webView" Source="{Binding BookingUrl}"
WidthRequest="1000" HeightRequest="1000">
我想解决用户在打开此页面时可能遇到的一些错误:无法访问互联网、超时、服务器不可用……
为此,我使用 EventToCommandBehavior
访问 ViewModel 中的事件 Navigating
和 Navigating
。
所以我的 XAML 看起来像这样:
<WebView x:Name="webView" Source="{Binding AvilaUrlBooking}"
WidthRequest="1000" HeightRequest="1000">
<WebView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="Navigating"
Command="{Binding NavigatingCommand}" />
<behaviors:EventToCommandBehavior
EventName="Navigated"
Command="{Binding NavigatedCommand}" />
</WebView.Behaviors>
</WebView>
而 ViewModel 是这样的:
public ICommand NavigatingCommand
{
get
{
return new Xamarin.Forms.Command<WebNavigatingEventArgs>(async (x) =>
{
if (x != null)
{
await WebViewNavigatingAsync(x);
}
});
}
}
private Task WebViewNavigatingAsync(WebNavigatingEventArgs eventArgs)
{
if (!IsConnected)
ServiceErrorKind = ServiceErrorKind.NoInternetAccess;
IsBusy = true;
return Task.CompletedTask;
}
public ICommand NavigatedCommand
{
get
{
return new Xamarin.Forms.Command<WebNavigatedEventArgs>(async (x) =>
{
if (x != null)
{
await WebViewNavigatedAsync(x);
}
});
}
}
private Task WebViewNavigatedAsync(WebNavigatedEventArgs eventArgs)
{
IsBusy = false;
IsFirstDisplay = false;
switch (eventArgs.Result)
{
case WebNavigationResult.Cancel:
// TODO - do stuff here
break;
case WebNavigationResult.Failure:
// TODO - do stuff here
break;
case WebNavigationResult.Success:
// TODO - do stuff here
break;
case WebNavigationResult.Timeout:
// TODO - do stuff here
break;
default:
// TODO - do stuff here
break;
}
return Task.CompletedTask;
}
bool isFirstDisplay;
public bool IsFirstDisplay
{
get { return isFirstDisplay; }
set { SetProperty(ref isFirstDisplay, value); }
}
public BookingViewModel()
{
_eventTracker = new AppCenterEventTracker();
IsFirstDisplay = true;
Title = "Booking";
IsConnected = Connectivity.NetworkAccess == NetworkAccess.Internet;
Connectivity.ConnectivityChanged += OnConnectivityChanged;
}
如果我使用右 URL,在 iOS 和 Android 上一切正常。
但是,如果我使用“错误的”URL(例如缺少字符),这仅适用于 Android:这种情况WebNavigationResult.Failure
在WebViewNavigatedAsync()
中被捕获,但我在iOS.
WebViewNavigatedAsync()
=>这正常吗?
我实现了一个“刷新”按钮来管理“无法访问互联网”错误。这个按钮可以通过 ToolBarItem
访问,在 ViewModel 中是这样的:
public void Refresh(object sender)
{
try
{
var view = sender as Xamarin.Forms.WebView;
view.Reload();
}
catch (Exception ex)
{
}
}
但在这些情况下,我在激活飞行模式后也有两种不同的行为:
- 在 iOS,当没有互联网接入时:我不进入
WebViewNavigatedAsync()
,即使互联网接入再次可用并且我点击 “刷新”按钮,我只路过WebViewNavigatingAsync()
- 在Android,当没有互联网接入时:我输入
WebViewNavigatedAsync()
,当互联网再次可用时,我点击在 “刷新”按钮 上,我通过了WebViewNavigatingAsync()
和WebViewNavigatedAsync()
=> 这正常吗?有没有合适的方法来管理这个?
is this normal?
根据我的测试。是的,我得到了相同的结果,如果我们输入错误 url,webview 始终是 white-empty 在 iOS 中的视图。所以 NavigatedCommand
无法执行。
如果我们使用正确的 url,webview 可以执行 NavigatedCommand
,并且 运行 结果如下图所示。
Is there a proper way to manager this?
我们可以在 iOS 中为 webview 使用自定义渲染器来处理这种情况。
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace MyCarsourlView.iOS
{
[Obsolete]
class CustomWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement == null) { Delegate = new CustomWebViewDelegate(); }
}
}
}
internal class CustomWebViewDelegate : UIWebViewDelegate
{
#region Event Handlers
public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType)
{
//Could add stuff here to redirect the user before the page loads, if you wanted to redirect the user you would do the redirection and then return false
return true;
}
public override void LoadFailed(UIWebView webView, NSError error)
{
Console.WriteLine("\nIn AppName.iOS.CustomWebViewRenderer - Error: {0}\n", error.ToString()); //TODO: Do something more useful here
//Here, you can test to see what kind of error you are getting and act accordingly, either by showing an alert or rendering your own custom error page using basic HTML
}
public override void LoadingFinished(UIWebView webView)
{ //You could do stuff here such as collection cookies form the WebView }
#endregion
}
}
如果我输入错误url,LoadFailed
会被执行
我发现了另一种似乎有效的方法,基于以下链接:
它可能并不完美,尤其是当我需要从 ViewModel 访问所需的事件时。
所以我创建了一个继承自 WebView
的 CustomWebView
控件:
public class CustomWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(CustomWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public CustomWebViewErrorKind ErrorKind { get; set; }
public event EventHandler LoadingStart;
public event EventHandler LoadingFinished;
public event EventHandler LoadingFailed;
/// <summary>
/// The event handler for refreshing the page
/// </summary>
public EventHandler OnRefresh { get; set; }
public void InvokeCompleted()
{
if (this.LoadingFinished != null)
{
ErrorKind = WebViewErrorKind.None;
this.LoadingFinished.Invoke(this, null);
}
}
public void InvokeStarted()
{
if (this.LoadingStart != null)
{
ErrorKind = WebViewErrorKind.None;
this.LoadingStart.Invoke(this, null);
}
}
public void InvokeFailed(CustomWebViewErrorKind errorKind)
{
if (this.LoadingFailed != null)
{
ErrorKind = errorKind;
this.LoadingFailed.Invoke(this, null);
}
}
/// <summary>
/// Refreshes the current page
/// </summary>
public void Refresh()
{
OnRefresh?.Invoke(this, new EventArgs());
}
}
然后我有自定义 CustomWebView
:
CustomWkWebViewRenderer
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWkWebViewRenderer))]
namespace MyProject.iOS.Renderers
{
public class CustomWkWebViewRenderer : ViewRenderer<CustomWebView, WKWebView>
{
public CustomWkWebViewRenderer()
{
Debug.WriteLine($"CustomWkWebViewRenderer - Ctor");
}
WKWebView webView;
protected override void OnElementChanged(ElementChangedEventArgs<CustomWebView> e)
{
base.OnElementChanged(e);
Debug.WriteLine($"CustomWkWebViewRenderer - OnElementChanged()");
if (Control == null)
{
Debug.WriteLine($"CustomWkWebViewRenderer - OnElementChanged() - Control == null");
webView = new WKWebView(Frame, new WKWebViewConfiguration()
{
MediaPlaybackRequiresUserAction = false
});
webView.NavigationDelegate = new DisplayWebViewDelegate(Element);
SetNativeControl(webView);
Element.OnRefresh += (sender, ea) => Refresh(sender);
}
if (e.NewElement != null)
{
Debug.WriteLine($"CustomWkWebViewRenderer - OnElementChanged() - e.NewElement != null");
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
webView.NavigationDelegate = new DisplayWebViewDelegate(Element);
SetNativeControl(webView);
}
}
private void Refresh(object sender)
{
Debug.WriteLine($"CustomWkWebViewRenderer - Refresh()");
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
}
}
}
我还有 CustomWkWebViewNavigationDelegate
实现了这个渲染器的 WKNavigationDelegate
:
public class CustomWkWebViewNavigationDelegate : WKNavigationDelegate
{
private CustomWebView element;
public CustomWkWebViewNavigationDelegate(CustomWebView element)
{
Debug.WriteLine($"CustomWkWebViewNavigationDelegate - Ctor");
this.element = element;
}
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
Debug.WriteLine($"CustomWkWebViewNavigationDelegate - DidFinishNavigation");
element.InvokeCompleted();
//base.DidFinishNavigation(webView, navigation);
}
public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation)
{
Debug.WriteLine($"CustomWkWebViewNavigationDelegate - DidStartProvisionalNavigation");
element.InvokeStarted();
//base.DidStartProvisionalNavigation(webView, navigation);
}
[Export("webView:didFailProvisionalNavigation:withError:")]
public override void DidFailProvisionalNavigation(WKWebView webView, WKNavigation navigation, NSError error)
{
Debug.WriteLine($"CustomWkWebViewNavigationDelegate - DidFailProvisionalNavigation - error : {error}");
var errorKind = CustomWebViewErrorKind.None;
switch (error.Code)
{
case -1009: // no internet access
{
errorKind = CustomWebViewErrorKind.NoInternetAccess;
break;
}
case -1001: // timeout
{
errorKind = CustomWebViewErrorKind.Timeout;
break;
}
case -1003: // server cannot be found
case -1100: // url not found on server
default:
{
errorKind = CustomWebViewErrorKind.Failure;
break;
}
}
element.InvokeFailed(errorKind);
//base.DidFailProvisionalNavigation(webView, navigation, error);
}
}
有一个 CustomWebViewErrorKind
枚举可以让我在 ViewModel 中实现一个常见的错误管理:
public enum CustomWebViewErrorKind
{
None = 0,
NoInternetAccess = 1,
Failure = 2,
Timeout = 3,
Cancel = 8,
Other = 9
}
为了从 ViewModel 访问事件,我使用 EventToCommandBehavior
描述 there
所以,我已经公开了视图中的所有命令,如下所示:
<controls:CustomWebView x:Name="webView"
Source="{Binding MyUrlBooking}"
Uri="{Binding MyUrlBooking}"
WidthRequest="1000" HeightRequest="1000">
<WebView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="Navigating"
Command="{Binding NavigatingCommand}" />
<behaviors:EventToCommandBehavior
EventName="Navigated"
Command="{Binding NavigatedCommand}" />
<behaviors:EventToCommandBehavior
EventName="LoadingStart"
Command="{Binding LoadingStartCommand}" />
<behaviors:EventToCommandBehavior
EventName="LoadingFinished"
Command="{Binding LoadingFinishedCommand}" />
<behaviors:EventToCommandBehavior
EventName="LoadingFailed"
Command="{Binding LoadingFailedCommand}"
CommandParameter="{x:Reference webView}"
/>
</WebView.Behaviors>
</controls:CustomWebView>
最后,在我的 ViewModel 中,我为 Android 部分执行此操作:
public ICommand NavigatingCommand
{
get
{
return new Xamarin.Forms.Command<WebNavigatingEventArgs>(async (x) =>
{
if (x != null)
{
await WebViewNavigatingAsync(x);
}
});
}
}
private Task WebViewNavigatingAsync(WebNavigatingEventArgs eventArgs)
{
Debug.WriteLine($"BookingViewModel - WebViewNavigatingAsync()");
IsBusy = true;
return Task.CompletedTask;
}
public ICommand NavigatedCommand
{
get
{
return new Xamarin.Forms.Command<WebNavigatedEventArgs>(async (x) =>
{
if (x != null)
{
await WebViewNavigatedAsync(x);
}
});
}
}
private Task WebViewNavigatedAsync(WebNavigatedEventArgs eventArgs)
{
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync()");
IsBusy = false;
switch (eventArgs.Result)
{
case WebNavigationResult.Cancel:
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Cancel");
ErrorKind = CustomWebViewErrorKind.Cancel;
break;
case WebNavigationResult.Failure:
default:
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Failure");
IsConnected = Connectivity.NetworkAccess == NetworkAccess.Internet;
if (IsConnected)
{
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Failure : Failure");
ErrorKind = CustomWebViewErrorKind.Failure;
}
else
if (IsConnected)
{
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Failure : NoInternetAccess");
ErrorKind = CustomWebViewErrorKind.NoInternetAccess;
}
break;
case WebNavigationResult.Success:
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Success");
ErrorKind = CustomWebViewErrorKind.None;
IsFirstDisplay = false;
break;
case WebNavigationResult.Timeout:
Debug.WriteLine($"BookingViewModel - WebViewNavigatedAsync() - Timeout");
ErrorKind = CustomWebViewErrorKind.Timeout;
break;
}
return Task.CompletedTask;
}
我为 iOS 部分这样做:
public ICommand LoadingStartCommand
{
get
{
return new Xamarin.Forms.Command(async () =>
{
await WebViewLoadingStartAsync();
});
}
}
private Task WebViewLoadingStartAsync()
{
Debug.WriteLine($"BookingViewModel - WebViewLoadingStartAsync()");
IsBusy = true;
return Task.CompletedTask;
}
public ICommand LoadingFinishedCommand
{
get
{
return new Xamarin.Forms.Command(async () =>
{
await WebViewLoadingFinishedAsync();
});
}
}
private Task WebViewLoadingFinishedAsync()
{
Debug.WriteLine($"BookingViewModel - WebViewLoadingFinishedAsync()");
IsBusy = false;
return Task.CompletedTask;
}
public ICommand LoadingFailedCommand
{
get
{
return new Xamarin.Forms.Command<object>(async (object sender) =>
{
if (sender != null)
{
await WebViewLoadingFailedAsync(sender);
}
});
}
}
private Task WebViewLoadingFailedAsync(object sender)
{
Debug.WriteLine($"BookingViewModel - WebViewLoadingFailedAsync()");
var view = sender as CustomWebView;
var error = view.ErrorKind;
Debug.WriteLine($"BookingViewModel - WebViewLoadingFailedAsync() - error : {error}");
IsBusy = false;
return Task.CompletedTask;
}
像这样我能够管理错误,从 ViewModel 重试和刷新,即使它可能不是更好的解决方案...