Fantastic Fonts in Forms Without the Fuss
In the last article we talked about how to add custom fonts to any Xamarin.Forms app.
Despite the numerous starts and stops that I encountered, the process turned out to be pretty easy, all in all.
However, the XAML needed to add custom fonts is a little bit verbose.
<Label TextColor="#934293" Text="Swanky" FontSize="80" VerticalOptions="Center" HorizontalOptions="Center">
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS">Cabin Sketch</On>
<On Platform="Android">CabinSketch-Reg.ttf#Cabin Sketch</On>
</OnPlatform>
</Label.FontFamily>
</Label>
One issue is that the font name is not bindable to a property in a view model.
An while there's nothing wrong with having to list everything out in the <OnPlatform>
tag all the time, XAML is a verbose enough language, and I often look for ways to make it both more concise and expressive.
And that's what I want to talk about in this post ... how to implement the custom font functionality in a couple of different ways so the XAML is more readable and its intention is clearer.
These techniques are not only limited to the custom fonts however - they can be used any time you use <OnPlatform>
- or pick and choose some anytime you want to extend the reach of XAML without having to mess with the code-behind page.
All of the demo code that goes along with this article can be found on GitHub.
Styles, Styles, Styles!!
The first, and probably easiest way, to implement the more concise and expressive custom font XAML is by putting the FontFamily
definition into a style - and having the Label
you wish to have the custom font implement that style.
The style definition for the FontFamily
would look like the following:
<Style x:Key="SwankyFontStyle" TargetType="Label">
<Setter Property="FontFamily">
<Setter.Value>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS">Cabin Sketch</On>
<On Platform="Android">CabinSketch-Reg.ttf#Cabin Sketch</On>
</OnPlatform>
</Setter.Value>
</Setter>
</Style>
Then the label implementing the style would be defined as:
<Label TextColor="#934293" Text="Swanky" FontSize="80" VerticalOptions="Center" HorizontalOptions="Center"
Style="{StaticResource SwankyFontStyle}" />
The same old style definition and consumption as usual - the only thing that's interesting is that <OnPlatform>
can be used in the <Setter.Value>
.
Pros
The advantages of doing it this way are:
- The style can be reused throughout the app (depending, of course, on where you declare the style).
- Less mess in the "body" of the XAML - the custom font definition only appears once.
- The syntax used to declare the custom font in the style is the same as used in the
<Label>
.
Cons
The disadvantages are:
- Styles can be a pain! At the very least, in an app that uses many styles, it takes careful planning to be sure that all the appropriate styles inherit from each other without duplicating code (which is the whole point in the custom font using styles in the first place).
- No databinding can occur to change the font at runtime. Once it's declared - it's set. (There are dynamic styles - but that's using a different trick - the style value is still static.)
Velcro (aka Attached) Properties!!
Attached properties addresses the biggest shortcomings of defining the custom fonts in styles - that of not being able to databind and the difficulty of structuring a complex style hierarchy.
Two attached properties need to be defined for custom fonts - one for the font family, and one for the font file name. We need two because Android deals with loading fonts differently than iOS.
With that said - one such attached property implementation looks like the following:
public static class AttachedSwank
{
public static readonly BindableProperty CustomFontNameProperty = BindableProperty.CreateAttached("CustomFontName",
typeof(string), typeof(AttachedSwank), default(string), BindingMode.OneWay, propertyChanged: HandleCustomFontNameChanged);
static void HandleCustomFontNameChanged(BindableObject bindable, object oldValue, object newValue)
{
var attachedLabel = bindable as Label;
if (attachedLabel == null)
return;
if (newValue == null)
{
attachedLabel.FontFamily = Font.Default.FontFamily;
return;
}
string newFont = newValue.ToString();
if (RuntimePlatform == Device.Android)
{
newFont = $"{GetCustomFontFileName(attachedLabel)}#{newValue}";
}
attachedLabel.FontFamily = newFont;
}
public static void SetCustomFontName(Label label, string fontName)
{
label.SetValue(CustomFontNameProperty, fontName);
}
public static string GetCustomFontName(Label label)
{
return (string)label.GetValue(CustomFontNameProperty);
}
public static readonly BindableProperty CustomFontFileNameProperty = BindableProperty.CreateAttached("CustomFontFileName",
typeof(string), typeof(AttachedSwank), default(string), BindingMode.OneWay, propertyChanged: HandleCustomFontFileNameChanged);
static void HandleCustomFontFileNameChanged(BindableObject bindable, object oldValue, object newValue)
{
if (RuntimePlatform != Device.Android)
return;
var attachedLabel = bindable as Label;
if (attachedLabel == null)
return;
if (newValue == null)
{
attachedLabel.FontFamily = Font.Default.FontFamily;
return;
}
var newFont = $"{newValue}#{GetCustomFontName(attachedLabel)}";
attachedLabel.FontFamily = newFont;
}
public static void SetCustomFontFileName(Label label, string fileName)
{
label.SetValue(CustomFontFileNameProperty, fileName);
}
public static string GetCustomFontFileName(Label label)
{
return (string)label.GetValue(CustomFontFileNameProperty);
}
}
Well, that doesn't help too much on the whole brevity thing does it...
There are 2 properties - CustomFontName
and CustomFontFileName
. In the propertyChanged
handler for each, we're checking to see what the new value is and then updating the font if there is a value set. (The CustomFontFileName
is only for Android.)
Like I said ... not so brief on the code ...
But the whole point of this is to clean up the XAML ... and the XAML indeed is cleaner ... consuming those attached properties to make a custom font in a Label
looks like this:
<Label TextColor="#934293" Text="Swanky" FontSize="80" VerticalOptions="Center" HorizontalOptions="Center"
local:AttachedSwank.CustomFontName="Cabin Sketch"
local:AttachedSwank.CustomFontFileName="CabinSketch-Reg.ttf"/>
Not too bad ... it's pretty short and very readable.
Pros
The advantages of using attached properties for custom fonts are:
- Bindable. It is possible to change the
FontFamily
at runtime. - The syntax of consuming the custom font in XAML is concise and readable. And it's explicit in what the properties mean, there's no doubt in what
CustomFontName
andCustomFontFileName
mean. - Easy reuse - one could put the attached property implementation into its own library and use it across projects.
- Custom implementation logic. You are not constrained with how to implement the custom font. For example, if the font specified cannot be found - you can turn the color red, or load a different font, etc, etc.
Cons
Of course, not all that glitters is gold, the disadvantages:
- The attached property implementation is very verbose. I kind of blew that off before - but having to maintain more code isn't desirable in all cases.
- Hides the implementation of loading the custom fonts from the XAML. Previously, it was easy to see how the font was being loaded, as it was done in XAML ... now it's hidden in another file (or potentially another library) ... in C#.
Markup Extensions!!
The last method we are going to cover is XAML markup extensions. It seems to be that these things don't get a lot of love, and I don't know why ... essentially they are like an attached property, but not bindable. They let you tack-on functionality to a control via custom XAML.
Again, there is a C# component to these:
public class CustomFontExtension : IMarkupExtension
{
public string FontFileAndName { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (RuntimePlatform == Android)
return FontFileAndName;
return FontFileAndName.Split(new char[] { '#' })[1];
}
}
That's a lot better for the brevity!
Implement the IMarkupExtension
interface and then in the only required function: ProvideValue
- make sure to return the correct portion of a custom property FontFileAndName
.
Then to consume, we're looking at:
<Label TextColor="#934293" Text="Swanky" FontSize="80" VerticalOptions="Center" HorizontalOptions="Center"
FontFamily="{local:CustomFont FontFileAndName=CabinSketch-Reg.ttf#Cabin Sketch}"/>
That's pretty concise too!! We're always expecting FontFileAndName
to be the full font file name,followed by its internal font name - the same as you would specify on Android - but letting the markup extension break it apart for iOS.
Pros:
There are several advantages of using markup extensions:
- You can use the "real" Xamarin.Forms property instead of a custom one as you have to with attached properties. So in this case we're specifying the
FontFamily
- so there's no doubt what's being set. - The markup extension's purpose, if you name the markup extension well, is easily deduced - and thus what the extension will be "extending" the
FontFamily
property to do is apparent. In this case,FontFamily
is being extended to aCustomFont
. - The implementation of how the markup extension is up to you. In this case I'm relying on the full font file name and logical name being specified in one property. You could use multiple properties...
- Markup extensions can be used in style definitions. That means this technique can be combined with the first.
Cons:
Of course - there has to be disadvantages:
- No databinding.
- The implementation of how the font name is parsed is hidden from the XAML.
- The font name must be in an exact format and the font name & file name must be the same across platforms. (But depending on how you implement the markup extension, you may be able to work around this.)
Summary
Even though Xamarin.Forms provides a means to specify custom fonts right out of the box - it doesn't mean we have to use it! Especially when using XAML - a language which is nothing, if not verbose - it makes sense to think of ways to make your code more concise, legible, and its intent readily apparent.
These are just three ways of doing so - using custom fonts only as an example. There are times when each and every one of these may be appropriate to use, and other times when none are ... it's up to you on how you structure your code, and where you want the logic to reside.
Personally, I like my XAML to be short & easy to read - so I try to find ways to use attached properties or markup extensions when they make sense. But it is a balancing act between being concise in the XAML and adding too much abstraction to the app.