Search code examples
xamarinxamarin.formsdynamicresource

How to correctly update font size of controls using styles and dynamic resources in Xamarin.Forms?


I am trying to update the font size of my controls at runtime.

To achieve this, I am applying styles to my controls as dynamic resources. When a button is tapped, I find the style resources in the app that have a font size setter, modify the font-size value, and write the entire style back to the resource dictionary the style was found in.

Example of control:

<Label Text="Welcome to Xamarin.Forms!" 
               HorizontalOptions="CenterAndExpand"
               Style="{DynamicResource DynamicLabel}"/>

Example of style:

<Style x:Key="DynamicLabel" TargetType="Label">
        <Setter Property="TextColor" Value="Red" />
        <Setter Property="HorizontalTextAlignment" Value="Center" />
        <Setter Property="VerticalTextAlignment" Value="Center" />
        <Setter Property="FontSize" Value="23" />
</Style>

Code that is being called to update the styles:

private void ApplyScale(ResourceDictionary dictionary, double scaleFactor)
    {
        foreach (var resourceDictionary in dictionary.MergedDictionaries)
        {
            ApplyScale(resourceDictionary, scaleFactor);
        }

        Dictionary<string, Style> stylesToAdd = new Dictionary<string, Style>();

        foreach (KeyValuePair<string, object> resource in dictionary)
        {
            if (resource.Value is Style style)
            {
                foreach (var styleSetter in style.Setters)
                {
                    if (styleSetter.Property.PropertyName == nameof(Font.FontSize))
                    {
                        if (styleSetter.Value is OnPlatform<int> platformSetter)
                        {
                            foreach (var platform in platformSetter.Platforms)
                            {
                                var onPlatformFontSize = int.Parse(platform.Value.ToString());
                                styleSetter.Value = (int) Math.Round(onPlatformFontSize * scaleFactor);
                            }
                        }
                        else
                        {
                            var fontSize = int.Parse(styleSetter.Value.ToString());
                            styleSetter.Value = (int)Math.Round(fontSize * scaleFactor);
                        }
                        stylesToAdd.Add(resource.Key, style);
                    }
                }
            }
        }

        foreach (var style in stylesToAdd)
        {
            dictionary[style.Key] = style.Value;
        }
    }

I have created a test application to demonstrate the issue: https://github.com/King-Xero/XF-DynamicStyleFontSizeTest

The appearance of the controls with the styles applied do not reflect the changes made to those styles. The controls are definitely using the styles, as the colours in the styles appear on the controls. The styles are definitely updated when inspecting via breakpoints. But the controls behave as if they have a StaticResource applied to them instead of a DynamicResource. I'm not sure if this is an issue within my app, or if it an issue in Xamarin.Forms.


Solution

  • In the Xamarin.Forms repo in GitHub there's an Issue opened 19 days ago, and it's still on triage, but an workarround would be forcing the ResourceStyle update like this:

    Code Behind:

        private void IncreaseFont_OnClicked(object sender, EventArgs e)
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                var savedstyle = MyLabel.Style;
                MyLabel.Style = null;
                _fontScaler.IncreaseFontScaling();
                MyLabel.Style = savedstyle;
            });
        }
    

    Xaml:

        <Label x:Name="MyLabel"
               Text="Welcome to Xamarin.Forms!" 
               HorizontalOptions="CenterAndExpand"
               Style="{DynamicResource DynamicLabel}"/>
    

    This is not the way to do it tho, keep watching for updates on the GitHub Issue.