Reputation: 849
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
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,
Upvotes: 0