I am working on a Xamarin.Forms PCL project that displays posts in the form of a WebView so I add clickable content such as hashtags.
The problem I am having is the WebView doesn't adjust to the size of its content. The WebView does not load an actual site I am binding the HTML to each post in a ListView using
"<html><p>" + body + "</p></html>"
I tried researching for custom renderers and followed this tutorial and ended up with the following code
For Android I used
#pragma warning disable CS0618 // Type or member is obsolete
public class CustomWebViewAndroid : WebViewRenderer
{
static CustomWebView _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<Xamarin.Forms.WebView> e)
{
base.OnElementChanged (e);
_xwebView = e.NewElement as CustomWebView;
_webView = Control;
if (e.OldElement == null) {
_webView.SetWebViewClient (new ExtendedWebViewClient ());
}
}
}
And for iOS
public class CustomWebViewiOS : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
Delegate = new CustomWebViewDelegate(this);
}
}
public class CustomWebViewDelegate : UIWebViewDelegate
{
CustomWebViewiOS webViewRenderer;
public CustomWebViewDelegate(CustomWebViewiOS _webViewRenderer = null)
{
webViewRenderer = _webViewRenderer ?? new CustomWebViewiOS();
}
public override async void LoadingFinished(UIWebView webView)
{
var wv = webViewRenderer.Element as CustomWebView;
if (wv != null)
{
await System.Threading.Tasks.Task.Delay(100); // wait here till content is rendered
wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;
}
}
}
And then applied it in XAML as so
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<local:CustomWebView HeightRequest="20" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<local:CustomWebView.Source>
<HtmlWebViewSource Html="{Binding HtmlContent}"/>
</local:CustomWebView.Source>
</local:CustomWebView>
</StackLayout>
On Android it clips off half the post's body.
On iOS it doesn't space the posts correctly and cuts off the timestamp of the one above it. My guess is the height of the ViewCell adjusts to the content but doesn't take into consideration the other posts. Also for iOS if the text takes two lines it requires you to scroll to view the rest.
Firstly you should set the ListView's HasUnevenRows
to true, then I recommend
you to use Grid
to wrap your webView
and remove the HeightRequest
in XAML. You can refer to my XAML:
<local:MyListView x:Name="MyListView" HasUnevenRows="True">
<local:MyListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<local:AutoWebView>
<local:AutoWebView.Source>
<HtmlWebViewSource Html="{Binding}"/>
</local:AutoWebView.Source>
</local:AutoWebView>
</Grid>
</ViewCell>
</DataTemplate>
</local:MyListView.ItemTemplate>
</local:MyListView>
Do not use static identifier for _xwebView
and in LoadingFinished()
when we get the actual HeightRequest
, refresh the ViewCell
using ForceUpdateSize()
like:
public class MyWebViewAndroidRenderer : WebViewRenderer
{
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
Control.SetWebViewClient(new ExtendedWebViewClient(Element as AutoWebView));
}
class ExtendedWebViewClient : Android.Webkit.WebViewClient
{
AutoWebView xwebView;
public ExtendedWebViewClient(AutoWebView webView)
{
xwebView = webView;
}
async public override void OnPageFinished(Android.Webkit.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;
// Here use parent to find the ViewCell, you can adjust the number of parents depending on your XAML
(xwebView.Parent.Parent as ViewCell).ForceUpdateSize();
}
base.OnPageFinished(view, url);
}
}
}
We also need to refresh the cell when the webView finished loading:
public override void LoadingFinished(UIWebView webView)
{
var wv = webViewRenderer.Element as AutoWebView;
if (wv.HeightRequest < 0)
{
wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;
(wv.Parent.Parent as ViewCell).ForceUpdateSize();
}
}
To display HTML content you can use this it works like charm for me, we don't need to use renderers to fit the content size.
<Label BackgroundColor="White" Text="{Binding description}" TextType="Html"/>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With