Xamarin.Forms XAML Label Adjusts Font Size to Fit Width of Content

Xamarin.Forms Label controls do not have the capability to adjust their font size to accomodate the width of the content. You have to use a view renderer in the platform-specific project to accomplish this. This blog post shows how to customize a label control for use in XAML for the iOS platform.

Create the LabelWorld Application in Xamarin Studio

If you have any problems with the following steps, see Xamarin.Forms Quickstart.

  1. Launch Xamarin Studio. Find it in the Applications folder or via Spotlight.
  2. In Xamarin Studio, click New Solution…
  3. In the Choose a template for your new project dialog, click Cross-platorm App and then select the Xamarin.Forms App template.
  4. In the Configure your Xamarin.Forms App dialog, enter LabelWorld as your App Name. Leave iOS and Android Target Platforms checked. For Shared Code, select Use Portable Class Library.
  5. In the Configure your new project dialog, leave the Solution and Project names set to LabelWorld, choose a suitable location for the project, and then click the Create button.
  6. Run the app in the simulator by pressing the Start button (looks like a Play button):

    Welcome to Xamarin Forms

Add a XAML Page

  1. In Xamarin Studio’s left-hand pane, Solution, click the gear icon next to the LabelWorld project and select Add > New File…
  2. In the New File dialog, select Forms > Forms ContentPage Xaml, name the new file LabelWorldPage, and click the New button. This will add a XAML page named LabelWorldPage to the project. This includes a XAML page:
    <?xml version="1.0" encoding="UTF-8"?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="LabelWorld.LabelWorldPage">
        <ContentPage.Content>
        </ContentPage.Content>
    </ContentPage>
    

    and a partial class:

    using System;
    using System.Collections.Generic;
    
    using Xamarin.Forms;
    
    namespace LabelWorld
    {
        public partial class LabelWorldPage : ContentPage
        {
            public LabelWorldPage ()
            {
                InitializeComponent ();
            }
        }
    }
    
  3. Run the project. It look no different. We’ll fix that by replacing the contents of the App() method in the LabelWorld class.
  4. Edit the LabelWorld.cs file. Replace the contents of the App() method:
    using System;
    using Xamarin.Forms;
    
    namespace LabelWorld
    {
        public class App : Application
        {
    
            public App ()
            {
                // The root page of your application
                MainPage = new LabelWorldPage();
            }
    
            protected override void OnStart ()
            {
                // Handle when your app starts
            }
    
            protected override void OnSleep ()
            {
                // Handle when your app sleeps
            }
    
            protected override void OnResume ()
            {
                // Handle when your app resumes
            }
        }
    }
    
  5. Run the app again. Now you’ll get a blank screen in place of the “Welcome to Xamarin Forms!” label in the middle of the screen. Let’s fix that by adding a label to the XAML file.
  6. Replace the contents of the LabelWorldPage.xaml file to add four labels:
    <?xml version="1.0" encoding="UTF-8"?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="LabelWorld.LabelWorldPage"
                 Padding="0, 20, 0, 0">
        <ContentPage.Content>
            <StackLayout Orientation="Vertical" BackgroundColor="Yellow" Spacing="0">
                <Label BackgroundColor="Aqua"    FontSize="Large" Text="Lorem"/>
                <Label BackgroundColor="Fuchsia" FontSize="Large" Text="Lorem ipsum
    dolor sit amet."/>
                <Label BackgroundColor="Lime"    FontSize="Large" Text="Lorem ipsum
    dolor sit amet, consectetur adipiscing elit."/>
                <Label BackgroundColor="Silver"  FontSize="Large" Text="Lorem ipsum
    dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero."/>
            </StackLayout>
        </ContentPage.Content>
    </ContentPage>
    

    Notes:

    1. iOS-specific Padding had been added to the ContentPage to prevent the first label from overlapping with the status bar.
    2. You can continue text onto a subsequent line as long as the continued text starts in the first column of the file.
    3. The funky colors are so you can see where the labels and text start and stop.
  7. Run the app:
    MultiLineLabels
  8. If you like the multi-line labels, then you’ve come to the wrong place. Our goal here is to reduce the font size of the text so that the label contains only one line of text.

Add a Custom Label Class

  1. Add a custom label class to the LabelWorld project:
    using System;
    using Xamarin.Forms;
    
    namespace LabelWorld
    {
        public class SRLabel : Label {}
    }
    
  2. Modify the XAML page, LabelWorld.xaml to reference the LabelWorld namespace and use the new SRLabel class in place of Label:
    <?xml version="1.0" encoding="UTF-8"?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="LabelWorld.LabelWorldPage"
                 xmlns:local="clr-namespace:LabelWorld;assembly=LabelWorld"
                 Padding="0, 20, 0, 0">
        <ContentPage.Content>
            <StackLayout Orientation="Vertical" BackgroundColor="Yellow" Spacing="0">
                <local:SRLabel BackgroundColor="Aqua"
                               FontSize="Large"
                               Text="Lorem"/>
                <local:SRLabel BackgroundColor="Fuchsia"
                               FontSize="Large"
                               Text="Lorem ipsum dolor sit amet."/>
                <local:SRLabel BackgroundColor="Lime"
                               FontSize="Large"
                               Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
                <local:SRLabel BackgroundColor="Silver"
                               FontSize="Large"
                               Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    Integer nec odio. Praesent libero."/>
            </StackLayout>
        </ContentPage.Content>
    </ContentPage>
    
  3. When you run the app, you will still see the same multi-line labels. But that’s good. It means we added the new custom class correctly.

Add a Custom Renderer

  1. We have done nothing yet to change the font size to fit the width of the label. Now it’s time to fix that. Add a custom renderer to the LabelWorld.iOS project:
    using System;
    using System.ComponentModel;
    
    using LabelWorld;
    using LabelWorld.iOS;
    
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;
    using UIKit;
    using System.Drawing;
    
    [assembly: ExportRenderer (typeof (SRLabel), typeof (SRLabelRenderer))]
    
    namespace LabelWorld.iOS
    {
        public class SRLabelRenderer : LabelRenderer
        {
            // Override the OnElementChanged method so
            // we can tweak this renderer post-initial setup
            protected override void OnElementChanged (
                ElementChangedEventArgs<Label> e)
            {
                base.OnElementChanged (e);
    
                var label = Control as UILabel;
                if (label != null)
                {
                    label.AdjustsFontSizeToFitWidth = true;
                    label.Lines = 1;
                    label.BaselineAdjustment = UIBaselineAdjustment.AlignCenters;
                    label.LineBreakMode = UILineBreakMode.Clip;
                }
            }
        }
    }
    
  2. Run the project and you can see that we’ve now adjusted the font size of each label to fit the text in one line.
    adjusts-font-size-to-fit-width

We have used text which exaggerates the effect for demonstration purposes. In a real app, you would want to make only small adjustments of the font size. Large adjustments, as we’ve done here, are likely to make your app less usable.

What’s next?

  • Android version
  • In some cases, it would be useful to adjust the height of the label to reflect the font size. When you fetch font metrics in iOS, you can get only the original font size and metrics, not the adjusted size. Please leave a reply if you know how to do that.
Advertisements
This entry was posted in iPhone Dev and tagged , , , , . Bookmark the permalink.

7 Responses to Xamarin.Forms XAML Label Adjusts Font Size to Fit Width of Content

  1. Dam says:

    Android Render, please

  2. ken says:

    Android renderer i cant find the AdjustsFontSizeToFitWidth equivalent

  3. Thank you. This helped.

  4. Radovici says:

    Very helpful, thank you! I will work on the android version in a month or two and post my results back here.

  5. haha says:

    Can you give me the implementation for android.

  6. twestley says:

    I have not developed an Android version yet. Perhaps @Radovici will post an Android version.

  7. drunkprogram says:

    Just put something together, seems to work in my app although YMMY. It uses Vimal.4745’s calculateWidth code from https://forums.xamarin.com/discussion/67545/how-to-calculate-or-measure-width-of-a-string. Here’s the code in the droid renderer:

    protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
    {
    base.OnSizeChanged(w, h, oldw, oldh);
    SetScaleFactor(w / Resources.System.DisplayMetrics.ScaledDensity, h / Resources.System.DisplayMetrics.ScaledDensity);
    }

    private void SetScaleFactor(double viewWidth, double viewHeight, double minimumScaleFactor = 0.5)
    {
    var label = Control as TextView;
    if (label != null)
    {
    double height;
    double width;
    MeasureTextSize(label.Text, out width, out height);
    double scaleFactor = Math.Min(viewWidth / width, label.ScaleX);
    label.ScaleY = label.TextScaleX = (float)Math.Max(scaleFactor, minimumScaleFactor);
    }
    }

    public void MeasureTextSize(string text, out double width, out double height)
    {
    Rect bounds = new Rect();
    TextView textView = new TextView(Forms.Context);
    textView.Paint.GetTextBounds(text, 0, text.Length, bounds);
    width = bounds.Width() / Resources.System.DisplayMetrics.ScaledDensity;
    height = bounds.Height() / Resources.System.DisplayMetrics.ScaledDensity;
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s