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中实现是最好的方法(或者说唯一的方法) ) 。
我昨天花了几个小时找出为什么我无法访问我的混合 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中实现是最好的方法(或者说唯一的方法) ) 。