Auto/Custom height on Xamarin Forms WebView for Android and iOS
Xamarin Forms can sometimes be annoying with things that seem so simple and trivial, for example... Web Views can't have an automatic height set, for this you'll need a custom renderer for each device type you're building for.
A quick Google Search on dynamic height for a Xamarin Forms webview at first seems promissing, but you'll be left wanting more of the little details.
You'll more than likely find the Xamarin documentation on custom controls helpful but again lacking so more Googling would be needed.
And then you'll find this beauty of an answer given by Nevalenny on the Xamarin Forums, in this post, the forum user tells you how to set up an Extended Web View that sets its own height to the height of the content within it. All good!
In case the thread gets removed, here's the important stuff:
On iOS: you need to use custom Delegate (in my case it's ExtendedUIWebViewDelegate)
[assembly: ExportRenderer (typeof(ExtendedWebView), typeof(ExtendedWebViewRenderer))] namespace Core.iOS { public class ExtendedWebViewRenderer : WebViewRenderer { protected override void OnElementChanged (VisualElementChangedEventArgs e) { base.OnElementChanged (e); Delegate = new ExtendedUIWebViewDelegate (this); } } }
then override LoadingFinished with async and small delay to get whole ContentSize.Height (in case of big content it takes some time to render even after loading is finished)
public class ExtendedUIWebViewDelegate : UIWebViewDelegate { ExtendedWebViewRenderer webViewRenderer; public ExtendedUIWebViewDelegate (ExtendedWebViewRenderer _webViewRenderer = null) { webViewRenderer = _webViewRenderer ?? new ExtendedWebViewRenderer (); } public override async void LoadingFinished (UIWebView webView) { var wv = webViewRenderer.Element as ExtendedWebView; if (wv != null) { await System.Threading.Tasks.Task.Delay (100); // wait here till content is rendered wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height; } } }
On Android: you need to use custom Android.Webkit.WebViewClient
using WebView = Android.Webkit.WebView; [assembly: ExportRenderer (typeof(ExtendedWebView), typeof(ExtendedWebViewRenderer))] namespace Core.Droid { public class ExtendedWebViewRenderer : WebViewRenderer { static ExtendedWebView _xwebView = null; WebView _webView; class ExtendedWebViewClient : Android.Webkit.WebViewClient { public override async void OnPageFinished (WebView view, string url) { if (_xwebView != null) { int i = 10; while (view.ContentHeight == 0 && i-- > 0) // wait here till content is rendered await System.Threading.Tasks.Task.Delay (100); _xwebView.HeightRequest = view.ContentHeight; } base.OnPageFinished (view, url); } } protected override void OnElementChanged (ElementChangedEventArgs e) { base.OnElementChanged (e); _xwebView = e.NewElement as ExtendedWebView; _webView = Control; if (e.OldElement == null) { _webView.SetWebViewClient (new ExtendedWebViewClient ()); } } } }
Then you'll just have to add the ExtendedWebView class somewhere in your shared library:
public class ExtendedWebView : WebView {}
That should be all you need to get the extended web view up and running right? Nope. A small detail that was left out of the post on the Xamarin forums is that if you fail to set a widthrequest and a heightrequest on the ExtendedWebView, you'll more than likely get no content render on iOS and a huge blank space below the content on Android. Useful.
Here's a snippet that shows the implementation of our ExtendedWebView within the Xamarin Forms shared code:
var Body = new ExtendedWebView(); Body.HorizontalOptions = LayoutOptions.Fill; Body.VerticalOptions = LayoutOptions.StartAndExpand; // Without the two below lines, the view will not render on iOS, likewise, the ExtendedWebView needs these to calculate content height. Body.WidthRequest = 640d; Body.HeightRequest = 640d; var htmlSource = new HtmlWebViewSource(); htmlSource.SetBinding(HtmlWebViewSource.HtmlProperty, "HTMLDesc"); htmlSource.Html = "somehtmlcodehere"; Body.SetBinding(HtmlWebViewSource.HtmlProperty, "HTMLDesc"); Body.Source = htmlSource; Body.BindingContextChanged += (sender, args) => { htmlSource.BindingContext = htmlSource.BindingContext; };
That should be it to get it working, but if you run into trouble on iOS, you might want to try adding the following to the Info.plist file:
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
Published at 18 May 2016, 10:14 AM
Tags: Xamarin