Font size! It sounds like one of the simplest things to figure out, but it turns out that sizing font to make a label expand and fill the available space is actually quite challenging in Xamarin. To start, there aren’t any Forms systems built to take care of that for us, so we’re left on our own to figure it out. There are some platform-specific solutions for auto-sizing, but since those features of iOS and Android are basically functional opposites, this may not be the best option for a multiplatform project.

It’s a shame that Xamarin.Forms doesn’t have a common function for this yet, since I can think of a number of common use cases where you might want your text to fill all the space available to it. For the I Am project, the main visual focus on the screen is a centered text sentence, so I really want it to be as large as possible. Without filling the available space, it just doesn’t stand out.

Xamarin mobile app screenshots, left shows default font size, right shows after sizing to fit
Left: default font size | Right: after sizing to fit space

Inside this sentence, you can see that I’ve also placed a picker that needs its text sized to match the surrounding labels. Unfortunately, all the sizing solutions I’ve come across are focused on simple labels, and pickers are a combination of different native elements when rendered. If you assume the picker is a problem, you’re correct. But I figured out a solution that works for all of it, and I’m excited to share it with you.

(And if you’re super impatient, just check out Xamarin-Label-FontSizer on Github. 😉)

Things That Didn’t Work

I went through quite a few possible code solutions, and I think it’s worth going over them quickly to explain what didn’t work and why not before we look at what I actually implemented.

✖️ - Estimated Font Size

The Estimated Font Size sample project from the book Creating Mobile Apps with Xamarin.Forms by Charles Petzold was my first attempt at a quick sizing. (The book is a remarkably useful resource, but it can also be outdated at times.) I figured I didn’t need anything too complicated and thought this would be a nice, straightforward solution.

Basically, this takes the outer view containing the label, sizes it up, and then calculates a font size using this equation –

double lineHeight = 1.2
double charWidth = 0.5;
//...
int fontSize = (int)Math.Sqrt(view.Width * view.Height /
                            (charCount * lineHeight * charWidth));

Unfortunately, this was overly simplistic and failed to account for many details.

  • Accessibility issues mentioned in the Xamarin docs - If users change their device’s text sizing in their settings - modifiers like ‘Normal’, ‘Bigger’, or ‘Biggest’ - then the font size can be scaled beyond the constraints you calculate for.
  • The docs say that the default lineHeight of a label is 1.2 times the height of the label, but I’ve found no way to verify that, which sort of screws with some calculations.
  • The variable charWidth used in the code above is only reliable for the default font that uses the Latin alphabet (and even here it seems like a rough guess). Relying on these defaults throws font styling and localization out the window. In case you need visual proof:
Xamarin mobile app screenshot shows different character widths between English and Japanese fonts
clearly different character widths

I did try to calculate the active font’s character width by measuring the text within the label, but the results were very inconsistent. This option was simply not working for me.

✖️ - Platform-Specific Sizing

There are some platform-specific options available for auto-sizing a label font, either by making a custom renderer or styling elements with an effect. But these didn’t work out for me, and here’s why:

  • They size labels only. You’d need a more extensive custom control for these to work with anything else - like my Picker.

  • iOS shrinks to fit while Android expands to fit. I never got far enough here to figure out how to accommodate these opposing setups.

  • I was pretty confused about how the native features worked and couldn’t find good Xamarin documentation. Sometimes the reasons really are that simple.

  • On iOS: The auto-sizing functionality on iOS is limited to single-line labels. (Apple docs) I needed more options.

  • On Android: The functions to size text are different enough in the Android and Xamarin docs that I wasn’t sure how to implement them. Maybe this is on me, but I couldn’t get the Android settings functioning at all.

✖️ - Empirical Font Size (original version)

The Empirical Font Size sample project was built to solve the accessibility issues in the Estimated Font Size sample from above and to perform somewhat safer font sizing calculations. I came back to this method a few different times to compare it to other options (and I eventually made an improved version of it which is discussed next).

The latest version of this code sample uses the VisualElement.Measure() function to size a label algorithmically within an outer container space. Basically, it sets a min and max font size, runs Measure() to check for fit, and repeats until it narrows the size down to on that fits. The results of the sizing looked something like this:

Xamarin mobile app screenshot showing sample text sized with Empirical Font Size project code
Empirical Font Size project sample

This was the most reliable code I found, but it was missing three things:

  • The algorithm parameters were a little stiff. They relied on actual View elements for constraint sizing, and I needed more flexibility. (easy fix)
  • The algorithm didn’t check to see if words were split over multiple lines, resulting in some nasty wrapping.
  • It could break bindings. 😱 The sizing algorithm only works when it performs calculation with a Label object directly. If you perform the calculations on a label with bound data, you must avoid setting that bound value in code for any reason or you will destroy the binding. Of course, you could always be sure to reset the binding after a sizing, but that’s costly.

But hey, this project sample was a good start for what I needed. I knew that with a little effort I could solve these issues and make an algorithm suited to my needs - and perhaps yours, too!

✔️ - The Font Sizer Project

I’ve started an open-source project called the Xamarin-Label-FontSizer that includes my improvements to the Empirical Font Sizing sample. Let’s go through what I changed.

  1. I refactored the algorithm into its own standalone function (and its own project as well). Instead of calculating fit with the surrounding elements, you can now use int and double constraint values for font and container sizes. (You still need to pass in a label, though. More on that in a moment.)
//My new function
public static double CalculateMaxFontSize(Label label, int minFontSize, int maxFontSize, double containerWidth, double containerHeight, bool wordSafe = true, bool sizeForWidth = false) {}

After you include the project in your solution, you just have to call FontSizer.CalculateMaxFontSize() and store the return value.

double labelFontSize = FontSizer.CalculateMaxFontSize(myLabel, 10, 200, stackLayout.Width, stackLayout.Height / 3);

myLabel.FontSize = labelFontSize;

Something like that should do the trick. However, this particular example still runs the risk of breaking bindings. The last point deals with this.

  1. I improved the function to account for word splitting managed by the variables wordSafe and sizeForWidth, so on top of the existing height check, there is now a width check for the longest word in the label string.

These values work on their own and are not intended for you to set when you call the sizing function. (For more details, check out the project readme.) With these additions to the calculations, you no longer have to worry about long words being displayed on multiple lines.

Xamarin mobile app screenshot showing word sizing with and without word safe enabled
Word safe | top: disabled | bottom: enabled
  1. Avoid breaking bindings by using what I’m calling ‘ghost sizing’. This is really not an improvement, but a rather safe workaround to the binding issue.
Mr Mít, the ghost sizer
Mr Mít, the ghost sizer

Both the original algorithm and my improved word-safe version modify the .Text property of the label you pass into the function. I’d say this is bad form, but the Measure() function requires a label object that is placed within a layout to perform any kind of sizing. Without that, nothing works. I tried finding ways around using a label by performing different calculations, but it just isn’t possible right now within Forms.

The solution I’ve found is to size using a ‘ghost label’. What I mean is to add an invisible label somewhere on your page with whatever properties you want and size that instead of the actual label you’re trying to fit into the space.

<!--  for sizing other elements without breaking bindings  -->
<Label x:Name="ghostLabel" IsVisible="false" />
Mr Mít sizing a ghost label
Mr Mít sizing a ghost label

You can set the Text of the ghost label to whatever text you’re trying to size (from a different label) and pass the ghost label into the function instead. It may not be visible on your page, but it does size correctly!

ghostLabel.Text = String.Copy(myLabel.Text);

double labelFontSize = FontSizer.CalculateMaxFontSize(ghostLabel, 10, 200, stackLayout.Width, stackLayout.Height / 3);

myLabel.FontSize = labelFontSize;

And honestly, it’s not such a bad workaround as its quite easy to implement and not resource-intensive at all.

Sizing The Picker

Remember the picker I wanted to size? Well, I can’t pass it into a function that only takes labels, and I can’t assume it will size the same, either. Thankfully, the ghost sizing solution works here as well. All you have to do is get the longest picker item, give it to the ghost label, and modify the size constraints to account for some padding between the text and the edges of the picker.

pickerFontSize = FontSizer.CalculateMaxFontSize(ghostLabel, 10, 200, stackLayout.Width * 0.9, stackLayout.Height / 2);

It’s the stackLayout.Width * 0.9 that gives me the padding I need by ensuring the text size only takes up a maximum 90% of the available width. Sure, it’s kind of a hack. But it works.

Contribute to the Font Sizer!

This Xamarin-Label-FontAutoSize project is open source and ready to download on Github. I’m sure I’ll be making updates to it as I have the time or need, and I’d love to get some input on how to approach the various issues that still exist.

The Github project readme goes over these things to watch out for if you decided to implement my work into your own codebase. If you think you can solve any of these issues, I invite you to contribute to the project yourself.

On The Horizon - Possible New Font Sizing Option in Xamarin.Forms

It looks like the Xamarin dev team might finally be considering adding a font sizing solution to Xamarin.Forms. I’ve been keeping an eye on this enhancement post and this pull request for any updates. I agree that this enhancement is needed, but I also wonder if it will take care of that crucial word splitting issue I fixed with the FontSizer. Or if it will be limited to sizing for a single line. Or how it will handle localized strings. I just don’t know!

But hey, if the sizing problem is ever solved, I can always alter this project to focus on localization needs. Those will never go away. 😉