Matthew Pans
Matthew Pans

Reputation: 849

.NET MAUI: WebView Height Not Adjusting Dynamically in Custom WebView on iOS platform

I am using a custom WebView in my .NET MAUI application to display dynamic HTML content on iOS platform. The HTML content changes daily, and I need to adjust the height of the WebView accordingly.

Custom WebView Renderer (iOS)

Here’s my custom WebViewRenderer implementation:

public class DailySaintWebViewRenderer : ViewRenderer<DailySaintWebView, WKWebView>
{
    WKWebView _wkWebView;

    protected override void OnElementChanged(ElementChangedEventArgs<DailySaintWebView> e)
    {
        base.OnElementChanged(e);

        if (Control == null)
        {
            var config = new WKWebViewConfiguration();
            config.AllowsInlineMediaPlayback = true;
            _wkWebView = new WKWebView(Frame, config);
            //transparent background
            _wkWebView = new WKWebView(CGRect.Empty, config);
            _wkWebView.BackgroundColor = UIColor.Clear;
            _wkWebView.ScrollView.BackgroundColor = UIColor.Clear;
            _wkWebView.Opaque = false;
            _wkWebView.NavigationDelegate = new MyNavigationDelegate();
            _wkWebView.ScrollView.ScrollEnabled = false;
            SetNativeControl(_wkWebView);

            if (Device.Idiom == TargetIdiom.Tablet)
            {
                //when targeting on iPad, add this to force the iPad behavior
                _wkWebView.Configuration.DefaultWebpagePreferences.PreferredContentMode = WKContentMode.Mobile;
            }
            SetNativeControl(_wkWebView);
        }
    }

    public class MyNavigationDelegate : WKNavigationDelegate
    {
        public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
        {
            string fontSize = Device.Idiom == TargetIdiom.Phone ? "500%" : "320%";

            string injectCustomFontScript = @"
                let style = document.createElement('style');
                style.innerHTML = `
                    @font-face {
                        font-family: 'CustomFont';
                        src: url('Poppins-Light') format('truetype');
                    }
                    body, p, h1, h2, h3, h4, h5, h6 {
                        font-family: 'CustomFont', sans-serif !important;
                        color: white !important;
                    }
                `;
                document.head.appendChild(style);
            ";

            string adjustTextSizeScript = $"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '{fontSize}';";
            
            WKJavascriptEvaluationResult handler = (NSObject result, NSError err) =>
            {
                if (err != null)
                {
                    System.Console.WriteLine(err);
                }
                if (result != null)
                {
                    System.Console.WriteLine(result);
                }
            };

            webView.EvaluateJavaScript(adjustTextSizeScript, handler);
            webView.EvaluateJavaScript(injectCustomFontScript, handler);
        }


    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == "Url")
        {
            string finalHtml = Element.Url.Replace("width=\"640\"", "width=\"1000\"");
            Control.LoadHtmlString(finalHtml, null);
        }
    }
}

XAML Code: Complete Layout:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:ffimageloading="clr-namespace:FFImageLoading.Maui;assembly=FFImageLoading.Maui"
             xmlns:fftransformations="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Maui"
             x:Name="ContentPage"
             BackgroundColor="#F5F5F5"
             xmlns:local="clr-namespace:Views.Renderer"
             x:Class="Views.DailySaintPage">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS">
                <OnIdiom x:TypeArguments="Thickness">
                    <OnIdiom.Phone>0,-10,0,0</OnIdiom.Phone>
                    <OnIdiom.Tablet>0</OnIdiom.Tablet>
                    <OnIdiom.Desktop>0,-10,0,0</OnIdiom.Desktop>
                </OnIdiom>
            </On>
        </OnPlatform>
    </ContentPage.Padding>

    <ContentPage.Behaviors>
        <toolkit:StatusBarBehavior StatusBarColor="#0279B5" StatusBarStyle="DarkContent" />
    </ContentPage.Behaviors>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="8*" />
            <RowDefinition Height="82*" />
            <RowDefinition Height="10*" />
        </Grid.RowDefinitions>

        <!--Stack Layout-->
        <StackLayout
            Grid.Row="0"
            Orientation="Vertical"
            HorizontalOptions="FillAndExpand">

            <Grid BackgroundColor="#0191da">
                <Grid.RowDefinitions>
                    <RowDefinition Height="{OnIdiom Phone=60, Tablet=90, Desktop=60}" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="1.5*"/>
                    <ColumnDefinition Width="7*"/>
                    <ColumnDefinition Width="1.5*"/>
                </Grid.ColumnDefinitions>
                      //Header layout
            </Grid>
        </StackLayout>

        <!--main Content View-->
        <Grid
            Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>

            <Grid
                Grid.Row="0"
                HorizontalOptions="FillAndExpand"
                VerticalOptions="FillAndExpand"
                Padding="5">

                <Grid.RowDefinitions>
                    <RowDefinition Height="20*" />
                    <RowDefinition Height="80*" />
                </Grid.RowDefinitions>

                <StackLayout Grid.Row="0" Orientation="Vertical">
                            //Title content
                </StackLayout>

               <!-- iOS view -->
                <Frame 
                    x:Name="ios_webview_frame"
                    IsVisible="False"
                    Padding="5"
                    CornerRadius="{OnIdiom Phone=20, Tablet=30, Desktop=20}"
                    Margin="0,10,0,0"
                    BackgroundColor="#0091DA"
                    VerticalOptions="FillAndExpand"
                    Grid.Row="1">

                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>

                        <ScrollView
                            Grid.Row="0"
                            Orientation="Vertical"
                            VerticalOptions="FillAndExpand">

                            <VerticalStackLayout
                                Grid.Row="0"
                                Padding="5"
                                Spacing="10"
                                VerticalOptions="FillAndExpand"
                                BackgroundColor="#0091DA">

                                <!-- Title label -->
                                <Label
                                    x:Name="ios_title_label"
                                    VerticalOptions="Start"
                                    VerticalTextAlignment="Center"
                                    HorizontalOptions="CenterAndExpand"
                                    HorizontalTextAlignment="Center"
                                    FontFamily="Poppins-SemiBold"
                                    TextColor="White">
                                    <Label.FontSize>
                                        <OnIdiom x:TypeArguments="x:Double">
                                            <OnIdiom.Phone>28</OnIdiom.Phone>
                                            <OnIdiom.Tablet>44</OnIdiom.Tablet>
                                            <OnIdiom.Desktop>28</OnIdiom.Desktop>
                                        </OnIdiom>
                                    </Label.FontSize>
                                </Label>

                                <Grid 
                                    IsVisible="False"
                                    x:Name="ios_image_layout">
                                    
                                    <!-- Saint image -->
                                    <ffimageloading:CachedImage 
                                    x:Name="ios_saint_image"
                                    HorizontalOptions="Fill"
                                    VerticalOptions="CenterAndExpand"
                                    HeightRequest="{OnIdiom Phone=350, Tablet=525, Desktop=350}"
                                    WidthRequest="{OnIdiom Phone=400, Tablet=600, Desktop=400}"
                                    Aspect="AspectFill"
                                    Margin="5"/>

                                    <!-- Banner with Label on Top -->
                                    <Grid
                                        VerticalOptions="End"
                                        Margin="15,0,15,10"
                                        HorizontalOptions="FillAndExpand"
                                        HeightRequest="50">

                                        <!-- The banner image -->
                                        <ffimageloading:CachedImage
                                            Source="ic_dailysaintbanner_xx.png"
                                            Aspect="AspectFit" />

                                        <!-- The label overlaying the banner -->
                                        <ScrollView
                                            Orientation="Horizontal"
                                            HorizontalOptions="FillAndExpand">
                                            
                                            <Label
                                                x:Name="ios_saintname_label"
                                                TextColor="Black"
                                                MaxLines="1"
                                                HorizontalTextAlignment="Center"
                                                VerticalTextAlignment="Center"
                                                FontFamily="Poppins-Bold.ttf" >
                                                <Label.FontSize>
                                                    <OnIdiom x:TypeArguments="x:Double">
                                                        <OnIdiom.Phone>20</OnIdiom.Phone>
                                                        <OnIdiom.Tablet>30</OnIdiom.Tablet>
                                                        <OnIdiom.Desktop>20</OnIdiom.Desktop>
                                                    </OnIdiom>
                                                </Label.FontSize>
                                            </Label>
                                        </ScrollView>
                                    </Grid>
                                </Grid>

                                <!-- Main content -->
                                <local:DailySaintWebView 
                                    x:Name="ios_web_view"
                                    BackgroundColor="#0091DA"
                                    HorizontalOptions="FillAndExpand"
                                    VerticalOptions="FillAndExpand">
                                    <local:DailySaintWebView.HeightRequest>
                                        <OnIdiom x:TypeArguments="x:Double">
                                            <OnIdiom.Phone>3000</OnIdiom.Phone>
                                            <OnIdiom.Tablet>4500</OnIdiom.Tablet>
                                            <OnIdiom.Desktop>3000</OnIdiom.Desktop>
                                        </OnIdiom>
                                    </local:DailySaintWebView.HeightRequest>
                                </local:DailySaintWebView>

                            </VerticalStackLayout>
                        </ScrollView>

                        <!-- Audio frame -->
                        <Frame
                            x:Name="ios_audio_frame"
                            Grid.Row="1"
                            BackgroundColor="#014F78"
                            Padding="0"
                            CornerRadius="{OnIdiom Phone=30, Tablet=45, Desktop=30}"
                            Margin="0,0,0,15"
                            HorizontalOptions="CenterAndExpand"
                            VerticalOptions="EndAndExpand">

                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="25*" />
                                    <ColumnDefinition Width="50*" />
                                    <ColumnDefinition Width="25*" />
                                </Grid.ColumnDefinitions>

                            </Grid>
                                           //Audio frame layout
                            <Frame.WidthRequest>
                                <OnIdiom x:TypeArguments="x:Double">
                                    <OnIdiom.Phone>380</OnIdiom.Phone>
                                    <OnIdiom.Tablet>960</OnIdiom.Tablet>
                                    <OnIdiom.Desktop>380</OnIdiom.Desktop>
                                </OnIdiom>
                            </Frame.WidthRequest>
                            <Frame.HeightRequest>
                                <OnIdiom x:TypeArguments="x:Double">
                                    <OnIdiom.Phone>60</OnIdiom.Phone>
                                    <OnIdiom.Tablet>90</OnIdiom.Tablet>
                                    <OnIdiom.Desktop>60</OnIdiom.Desktop>
                                </OnIdiom>
                            </Frame.HeightRequest>
                        </Frame>
                    </Grid>
                </Frame>
            </Grid>
            <Grid.GestureRecognizers>
                <PinchGestureRecognizer PinchUpdated="OnPinchUpdated"/>
            </Grid.GestureRecognizers>
        </Grid>

        <!--Footer Layout-->
        <Grid
            Grid.Row="2"
            VerticalOptions="Center"
            BackgroundColor="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="25*" />
                <ColumnDefinition Width="25*" />
                <ColumnDefinition Width="25*" />
                <ColumnDefinition Width="25*" />
            </Grid.ColumnDefinitions>
                            //Footer Layout
            <Grid.HeightRequest>
                <OnIdiom x:TypeArguments="x:Double">
                    <OnIdiom.Phone>60</OnIdiom.Phone>
                    <OnIdiom.Tablet>90</OnIdiom.Tablet>
                    <OnIdiom.Desktop>60</OnIdiom.Desktop>
                </OnIdiom>
            </Grid.HeightRequest>
        </Grid>
    </Grid>
</ContentPage>

I tried setting the WebView height dynamically based on the HTML content in my .NET MAUI application. Below is the custom renderer I implemented for iOS using WKWebView. However, the height is not adjusting correctly. Could you review my approach and suggest any improvements?

Tried Approach:

Custom WebView Renderer:

public class DailySaintWebViewRenderer : ViewRenderer<DailySaintWebView, WKWebView>
{
    WKWebView _wkWebView;

    protected override void OnElementChanged(ElementChangedEventArgs<DailySaintWebView> e)
    {
        base.OnElementChanged(e);

        if (Control == null)
        {
            var config = new WKWebViewConfiguration();
            config.AllowsInlineMediaPlayback = true;
            _wkWebView = new WKWebView(Frame, config);
            //transparent background
            _wkWebView = new WKWebView(CGRect.Empty, config);
            _wkWebView.BackgroundColor = UIColor.Clear;
            _wkWebView.ScrollView.BackgroundColor = UIColor.Clear;
            _wkWebView.Opaque = false;
            _wkWebView.NavigationDelegate = new MyNavigationDelegate(this);
            _wkWebView.ScrollView.ScrollEnabled = false;
            SetNativeControl(_wkWebView);

            if (Device.Idiom == TargetIdiom.Tablet)
            {
                //when targeting on iPad, add this to force the iPad behavior
                _wkWebView.Configuration.DefaultWebpagePreferences.PreferredContentMode = WKContentMode.Mobile;
            }
            SetNativeControl(_wkWebView);
        }
    }

    public class MyNavigationDelegate : WKNavigationDelegate
    {
         DailySaintWebViewRenderer dailysaintWebViewRenderer;

        // Constructor to receive reference of MyWebViewRenderer
        public MyNavigationDelegate(DailySaintWebViewRenderer webViewRenderer)
        {
            dailysaintWebViewRenderer = webViewRenderer;
        }
        public async override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
        {
            string fontSize = Device.Idiom == TargetIdiom.Phone ? "500%" : "320%";

            string injectCustomFontScript = @"
                let style = document.createElement('style');
                style.innerHTML = `
                    @font-face {
                        font-family: 'CustomFont';
                        src: url('Poppins-Light') format('truetype');
                    }
                    body, p, h1, h2, h3, h4, h5, h6 {
                        font-family: 'CustomFont', sans-serif !important;
                        color: white !important;
                    }
                `;
                document.head.appendChild(style);
            ";

            string adjustTextSizeScript = $"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '{fontSize}';";

            WKJavascriptEvaluationResult handler = (NSObject result, NSError err) =>
            {
                if (err != null)
                {
                    System.Console.WriteLine(err);
                }
                if (result != null)
                {
                    System.Console.WriteLine(result);
                }
            };

            webView.EvaluateJavaScript(adjustTextSizeScript, handler);
            webView.EvaluateJavaScript(injectCustomFontScript, handler);

            // Dynamically adjust WebView height
            var wv = dailysaintWebViewRenderer.Element as DailySaintWebView;
            if (webView != null && webView.ScrollView != null && webView.ScrollView.ContentSize != null)
            {
                await Task.Delay(200); // Wait for rendering to complete
                wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;
            }
        }


    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == "Url")
        {
            string finalHtml = Element.Url.Replace("width=\"640\"", "width=\"1000\"");
            Control.LoadHtmlString(finalHtml, null);
        }
    }
}

XAML Implementation (Without HeightRequest)

<local:DailySaintWebView 
         x:Name="ios_web_view"
         BackgroundColor="#0091DA"
         HorizontalOptions="FillAndExpand"
         VerticalOptions="FillAndExpand">
</local:DailySaintWebView>

I want the WebView height to automatically adjust based on the HTML content’s height instead of setting a fixed HeightRequest. Since the content changes daily, I cannot hardcode a specific height.

Code for DailySaintWebView:

public class DailySaintWebView : WebView
{
    public static readonly BindableProperty UrlProperty = BindableProperty.Create(
     propertyName: "Url",
     returnType: typeof(string),
     declaringType: typeof(DailySaintWebView),
     defaultValue: default(string));

    public string Url
    {
        get { return (string)GetValue(UrlProperty); }
        set { SetValue(UrlProperty, value); }
    }
}

Update:

Currently using Custom WebView Renderer: (DailySaintRenderer)

public class DailySaintWebViewRenderer : ViewRenderer<DailySaintWebView, WKWebView>
{
    WKWebView _wkWebView;

    protected override void OnElementChanged(ElementChangedEventArgs<DailySaintWebView> e)
    {
        base.OnElementChanged(e);

        if (Control == null)
        {
            var config = new WKWebViewConfiguration();
            config.AllowsInlineMediaPlayback = true;
            _wkWebView = new WKWebView(Frame, config);
            //transparent background
            _wkWebView = new WKWebView(CGRect.Empty, config);
            _wkWebView.BackgroundColor = UIColor.Clear;
            _wkWebView.ScrollView.BackgroundColor = UIColor.Clear;
            _wkWebView.Opaque = false;
            _wkWebView.NavigationDelegate = new MyNavigationDelegate(this);
            _wkWebView.ScrollView.ScrollEnabled = false;
            SetNativeControl(_wkWebView);

            if (Device.Idiom == TargetIdiom.Tablet)
            {
                //when targeting on iPad, add this to force the iPad behavior
                _wkWebView.Configuration.DefaultWebpagePreferences.PreferredContentMode = WKContentMode.Mobile;
            }
            SetNativeControl(_wkWebView);
        }
    }

    public class MyNavigationDelegate : WKNavigationDelegate
    {
        DailySaintWebViewRenderer dailySaintWebViewRenderer;

        // Constructor to receive reference of MyWebViewRenderer
        public MyNavigationDelegate(DailySaintWebViewRenderer webViewRenderer)
        {
            dailySaintWebViewRenderer = webViewRenderer;
        }
        public async override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
        {
            string fontSize = Device.Idiom == TargetIdiom.Phone ? "500%" : "320%";

            string injectCustomFontScript = @"
                let style = document.createElement('style');
                style.innerHTML = `
                    @font-face {
                        font-family: 'CustomFont';
                        src: url('Poppins-Light') format('truetype');
                    }
                    body, p, h1, h2, h3, h4, h5, h6 {
                        font-family: 'CustomFont', sans-serif !important;
                        color: white !important;
                    }
                `;
                document.head.appendChild(style);
            ";

            string adjustTextSizeScript = $"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '{fontSize}';";
            WKJavascriptEvaluationResult handler = (NSObject result, NSError err) =>
            {
                if (err != null)
                {
                    System.Console.WriteLine(err);
                }
                if (result != null)
                {
                    System.Console.WriteLine(result);
                }
            };

            webView.EvaluateJavaScript(adjustTextSizeScript, handler);
            //webView.EvaluateJavaScript(changeTextColorScript, handler);
            webView.EvaluateJavaScript(injectCustomFontScript, handler);

             // Dynamically adjust WebView height
             try
             {
                var wv = dailySaintWebViewRenderer.Element as DailySaintWebView;
                if (webView != null && webView.ScrollView != null && webView.ScrollView.ContentSize != null)
                {
                    await Task.Delay(400); // Wait for rendering to complete
                    wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;
                }
             }
            catch (Exception ex)
            {
                Debug.WriteLine("WebViewheightexceptions>>:" + ex);
            }
        }
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == "Url")
        {
            var dailysaintWebView =  sender as DailySaintWebView;
            if (dailysaintWebView != null)
                dailysaintWebView.HeightRequest = 0;

            string finalHtml = Element.Url.Replace("width=\"640\"", "width=\"1000\"");
            Control.LoadHtmlString(finalHtml, null);
        }
    }
}

Upvotes: 0

Views: 166

Answers (1)

Liqun Shen-MSFT
Liqun Shen-MSFT

Reputation: 8220

Update

I made some changes OnElementPropertyChanged method and which make it works. Please try the following code,

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    base.OnElementPropertyChanged(sender, e);
    
    if (e.PropertyName == "Url")
    {
        // add the following code  
        var dailysaintWebView =  sender as DailySaintWebView;
        if (dailysaintWebView != null)
            dailysaintWebView.HeightRequest = 0;


        string finalHtml = Element.Url.Replace("width=\"640\"", "width=\"1000\"");
        Control.LoadHtmlString((NSString)htmlContent, null);
    }
}

And the custom renderer you use should work.

        // Dynamically adjust WebView height
        var wv = dailysaintWebViewRenderer.Element as DailySaintWebView;
        if (webView != null && webView.ScrollView != null && webView.ScrollView.ContentSize != null)
        {
            await Task.Delay(200); // Wait for rendering to complete
            wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;
        }

You may consider modifying the htmlstring a bit.

You may add <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no'> to the html string like MyWebViewRenderer.

You can also set the font size for the webview like <style>html{font-size:8px}</style> to adjust the text size in the WebView.

Here is just an example for htmlstring,

@"<!DOCTYPE html><html><meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0, user-scalable=no'><style>html{font-size:8px}</style><body>......

Hope it helps!


Here is the effect,

enter image description here

Upvotes: 0

Related Questions