Setting height of WebView dynamically

Hi,

I have random HTML content coming from service which I am displaying in a webview in a xamarin forms application. Now, I want to adjust the height of this webview as per the content.

Currently I am setting the height based on the length of HTML but it does not give correct results.

Can you suggest an alternate way to achieve the same?

Thanks
Deepesh.

Posts

  • Suresh.5394Suresh.5394 USMember

    looking for the same on xamarin forms. To set height of web view based on Content. can any body help on it.

    Thanks
    Suresh

  • I am having the same issue.

  • ChaseFlorellChaseFlorell CAInsider, University mod

    I've got a custom renderer that does what you're looking for. It's not perfect, but it works... just remove the markdown conversion stuff.

    https://github.com/FloMediaGroup/fmg-xamarin-forms/tree/master/src/Fmg.XamForms.Renderers.Markdown

    Most specifically

    // https://github.com/FloMediaGroup/fmg-xamarin-forms/blob/master/src/Fmg.XamForms.Renderers.Markdown/Fmg.XamForms.Renderers.Markdown.Droid/MarkdownRenderer.cs#L97
        private async void ResizeWebView(object sender, EventArgs e)
        {
            if (!_needsRedraw || Element == null) return;
    
            var newContentHeight = _webView.ContentHeight;
    
            if (newContentHeight == _oldHeight || newContentHeight == 0) return;
    
            var bounds = new Rectangle(Element.Bounds.X, Element.Bounds.Y, Element.Bounds.Width, newContentHeight);
            await Element.LayoutTo(bounds, Element.TransitionSpeedMsec, Element.Easing);
            Element.HeightRequest = newContentHeight;
    
            // todo: FIX ME
            // not sure why there's the odd case where the height is 8.
            if (newContentHeight == 8)
            {
                _webView.Reload();
                _oldHeight = -1;
                return;
            }
    
            _oldHeight = newContentHeight;
            _needsRedraw = false;
        }
    
  • MichaelLeung.7495MichaelLeung.7495 USMember ✭✭

    Thanks! That is nearly prefect, but that is not 100% for me.
    I found the height is incorrect in some test cases.
    I don't know why. I used a local string which contains html content , maybe that cause problems.

    @nevalenny said:
    You need to create custom renderers to resize WebView dynamically:

    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<Xamarin.Forms.WebView> e)
            {
                base.OnElementChanged (e); 
                _xwebView = e.NewElement as ExtendedWebView;
                _webView = Control;
    
                if (e.OldElement == null) {                
                    _webView.SetWebViewClient (new ExtendedWebViewClient ());
                }         
    
            }        
        }
    }
    
  • DepechieDepechie BEInsider, Developer Group Leader ✭✭
    edited August 2016

    @michaelleung.7495 when using a small local html text with double div tags, this still doesn't work... any idea?

  • RussellFustinoRussellFustino USUniversity ✭✭
    edited February 2017

    The Android version works perfect. On the iOS version, the ScrollView.ContentSize.Height is coming up as 0 when stepping thru the code. I am setting the source property of the webview to HtmlWebViewSource() in the PCL as follows, so it is not a link to a page, but the content of the page itself.

           var htmlSource = new HtmlWebViewSource();
            htmlSource.Html = @"<html><head><meta name='viewport' content='width=device-width; height=device-height; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;'/></head><body>" + viewModel.Treatment.indications.ToString() + "</body></html>";
    
            CustomWebViewIndications.Source = htmlSource;
    

    I can not seem to figure our how to modify this line in iOS to pick that up?

    wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;

    Also, anyone have the code for UWP?

  • RussellFustinoRussellFustino USUniversity ✭✭

    Figured out the iOS bug I had, needed to set the HeightRequest initially in the PCL code to something small, like 20. This way a ScrollView would be created. Still looking for a UWP solution.

  • ChristianReicheltChristianReichelt DEMember ✭✭

    hey,
    this is really working great, but when I use the new ExtendedUIWebViewDelegate, the WebView Notifier like Navigated and Navigating aren't called anymore. I assume they aren't connected to the new delegate but I don't know how to realize that.
    Any ideas?

  • TimAbbsTimAbbs USMember

    ChristianReichelt, did you figure out a workaround?

  • try to override bool ShouldStartLoad() till you get a better answer.

    public override bool ShouldStartLoad(UIWebView webView, Foundation.NSUrlRequest request, UIWebViewNavigationType navigationType) 
    {
        if ( navigationType == LinkClicked) do your stuff and return true;
    }
    

    request is your clicked Url.
    hope it helps you

  • CaioshinCaioshin ITMember ✭✭
    edited May 2017

    great suggestion RusselFustino, setting height on iOS solve the problem using this renderer :-)

    <!--Need to set this because the renderer is not enough on Ios, container must be initializated with a fake height--> <localControls:ExtendedWebView.HeightRequest> <OnPlatform iOS="30" x:TypeArguments="x:Double" /> </localControls:ExtendedWebView.HeightRequest>

  • Srinig29Srinig29 INMember

    This works fine for Android and IOS. I'm also having the same issue in UWP. Do you guys have any solution for this?

  • GertJanSchoneveldGertJanSchoneveld NLMember ✭✭

    We use MichaelLeung.7495 his solution, but lately this was broken on Android phones. We solved this by adding a _webView.Reload(); at the end of OnElementChanged.

  • HiramAhlgrenHiramAhlgren SEMember

    Hi! Has anybody solved this problem for UWP?

  • rutul2211rutul2211 INMember ✭✭

    This custom renders not properly works inside ScrollView. I have implemented in android. It takes more space. Has anyone found any workaround?

  • HMAKHMAK USMember ✭✭

    I am running the WebViewCustomeRenderer for Android, but the ContentHeight on the Android.WebKit.WebView doesnt match the real content height.. Anybody ran into the same issue?

  • TiSebTiSeb USMember ✭✭
    edited December 2017

    @nevalenny @GertJanSchoneveld Thx a lot, the combined solution works perfectly with adding
    using ExtentedWebView = Xamarin.Forms.WebView; into Renderers & Delegates Classes ;)

  • WarwenczackWarwenczack BRMember ✭✭

    @nevalenny said:
    You need to create custom renderers to resize WebView dynamically:

    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<Xamarin.Forms.WebView> e)
            {
                base.OnElementChanged (e); 
                _xwebView = e.NewElement as ExtendedWebView;
                _webView = Control;
    
                if (e.OldElement == null) {                
                    _webView.SetWebViewClient (new ExtendedWebViewClient ());
                }         
    
            }        
        }
    }
    

    looks like a good awnser, but dnt work here :'(
    anyone have something like this to solve this problem ?

  • WarwenczackWarwenczack BRMember ✭✭

    @MichaelLeung.7495 said:
    Thanks! That is nearly prefect, but that is not 100% for me.
    I found the height is incorrect in some test cases.
    I don't know why. I used a local string which contains html content , maybe that cause problems.

    @nevalenny said:
    You need to create custom renderers to resize WebView dynamically:

    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<Xamarin.Forms.WebView> e)
            {
                base.OnElementChanged (e); 
                _xwebView = e.NewElement as ExtendedWebView;
                _webView = Control;
    
                if (e.OldElement == null) {                
                    _webView.SetWebViewClient (new ExtendedWebViewClient ());
                }         
    
            }        
        }
    }
    

    you can set the System.Threading.Tasks.Task.Delay bigger, i think that this solve.

  • DaveDeJongDaveDeJong NLMember ✭✭
    edited April 15

    There is an Android issue with nevalenny's solution when you have multiple webviews on a form.

    This is because of the static _xwebview variable. I solved this by removing the static variable and passing the forms webview in the constructor of WebViewClient:

    public class ExtendedWebViewRenderer : WebViewRenderer
    {
        public ExtendedWebViewRenderer(Context context) : base(context)
        {
        }
    
        class ExtendedWebViewClient : WebViewClient
        {
            private readonly ExtendedWebView _xWebView;
    
            public ExtendedWebViewClient(ExtendedWebView xWebView)
            {
                _xWebView = xWebView;
            }
    
            public override async void OnPageFinished (WebView view, string url)
            {
                if (_xWebView != null) {
                    var 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); 
            Control.SetBackgroundColor(new Android.Graphics.Color(255, 255, 255, 127));
    
            if (e.OldElement == null)
            {
                Control.SetWebViewClient(new ExtendedWebViewClient(e.NewElement as ExtendedWebView));
                var nativeWebView = Control;
                nativeWebView.Settings.JavaScriptEnabled = true;
            }         
        }        
    }
    
  • CharlesRoddieCharlesRoddie USMember ✭✭

    Rather than fixing with custom renderers, better to help Xamarin.Forms support this.
    https://github.com/xamarin/Xamarin.Forms/issues/1711

  • lHubertllHubertl Member
    edited September 16

    @nevalenny said:
    You need to create custom renderers to resize WebView dynamically:

    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<Xamarin.Forms.WebView> e)
            {
                base.OnElementChanged (e); 
                _xwebView = e.NewElement as ExtendedWebView;
                _webView = Control;
    
                if (e.OldElement == null) {                
                    _webView.SetWebViewClient (new ExtendedWebViewClient ());
                }         
    
            }        
        }
    }
    

    This solution works for me, but for Android, you made a critical mistake:
    static ExtendedWebView _xwebView = null;

    In this case, if in your page you have more then one web view, this solution will positively impact only the last control

    So this is my solution (a little bit changed due to my control, but without static)

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);
    
            if (e.NewElement is WebViewControl webViewControl)
            {
                if (e.OldElement == null)
                {
                    Control.SetWebViewClient(new ExtendedWebViewClient(webViewControl));
                }
            }
        }
    
        class ExtendedWebViewClient : Android.Webkit.WebViewClient
        {
            private readonly WebViewControl _control;
    
            public ExtendedWebViewClient(WebViewControl control)
            {
                _control = control;
            }
    
            public override async void OnPageFinished(WebView view, string url)
            {
                if (_control != null)
                {
                    int i = 10;
                    while (view.ContentHeight == 0 && i-- > 0) // wait here till content is rendered
                    {
                        await System.Threading.Tasks.Task.Delay(100);
                    }
    
                    _control.HeightRequest = view.ContentHeight;
                }
    
                base.OnPageFinished(view, url);
            }
        }
    
Sign In or Register to comment.