Reputation: 161
I spent some hours yesterday finding out why I couldn't access the (obviously working) params of my Hybrid wkWebView. In the iOS part under the correctly fired DidFinishNavigation override, I always got a NullObjectReference Exception when trying to EvaluateJavaScriptAsync or even access the Source parameter.
Then I finally found out that I was looking at the wrong object: I was accessing the Forms HybridWebView object (upwards in the "chain") instead of the iOS webview object (downwards from the renderer). In the code below the "webView" parameter from DidFinishNavingation istead of the _wkWebView from the webViewRenderer.Element (see last code part)
Now my code is working, but I'm still wondering whether my overall concept of the platform specific intergration is correct or whether the Forms functions available in the forms hybridwebview (such as EvaluateJavaScript) should be working directly? Am I'm missing some link to connect those with the underlying iOS webview?
My shared Forms code for the 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));
}
}
and the connected iOS part for the Renderer:
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);
}
}
and finally the DidFinishLoading event handler in iOS 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);
}
}
}
Upvotes: 0
Views: 321
Reputation: 18861
When we invoked the line
SetNativeControl(_wkWebView);
The WebView
that you define in Forms been initialized again . So you need to implement most of function in Custom Renderer . Especially in WebView of iOS, if you want to add listener the lifecycle of it or invoke some JS method , implement it in Custom Renderer is the best way (or the only way) .
Upvotes: 1