Using A Custom Icon Font in Xamarin.Forms

Anyone who has done much mobile development work knows the pain of managing multiple bitmaps for devices with different screen resolutions. It’s a particular bugbear for Android developers due to the plethora of different devices available and the fact that Google’s default method of choosing appropriate resources (screen density) is not always an accurate indicator of screen size.

It would be much better all round to use vector graphics instead, but as (at the time of writing) native support for SVGs on both iOS and Android is patchy at best it’s no surprise that there’s no vector graphics support in Xamarin.Forms.

Fortunately there’s a decent workaround for monochrome vector graphics – use a custom font instead. You can either use an existing icon font such as Google’s excellent Material Icons set or the ones from Font Awesome, or use a tool such as IcoMoon which enables you to create your own font from SVG files created in Illustrator or similar.

So here’s how you do it, for the purposes of this tutorial we’re going to be applying Google’s Material Icons font to the Xamarin.Forms.Label control but the same approach can be used for different fonts and controls (e.g. Xamarin.Forms.Button).

You can download a .zip file for the project here.

1. Create A Custom Control
Just so we don’t get confused we’re going to create our own subclass of Xamarin.Forms.Label called IconLabel. Add a new empty class file to your shared project like so…

using System;
using Xamarin.Forms;

namespace CustomFontDemo
{
	public class IconLabel:Label
	{
		public IconLabel ()
		{
		}
	}
}

This custom IconLabel will work exactly like a standard Xamarin.Forms.Label on the whole, but for the purposes of this tutorial I’m going to add an instance of it to the page that the default Xamarin.Forms app creates at startup. The resulting XAML looks like this..

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
			 xmlns:local="clr-namespace:CustomFontDemo" 
			 x:Class="CustomFontDemo.CustomFontDemoPage">
	<local:IconLabel Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>

Nothing remarkable here, if you run the app at this point it should operate exactly like the default Xamarin.Forms app. Now we’re going to get the icon font to work which requires a bit of platform-specific tinkering

2. Import Your Custom Font Into Your iOS Project
Right-click on the Resources directory in your iOS project and select ‘Add Files’. Navigate to the font file you have chosen on your hard drive and add it to the project. Once the file has been added right-click on it and check the ‘Build Action’ is set to ‘BundleResource’.

mockup_3x
Where To Import The Font File On iOS

Now select your ‘info.plist’ file and click the ‘Source’ tab. Right-click anywhere in the editor and select ‘New Key’. You need to change the name of the key from ‘Custom Property’ to ‘UiAppFonts’ and then add an entry where it says ‘String’ for the file name of your font. In this instance ‘materialicons.ttf’.

mockup_3x
Info.plist Settings On iOS

3. Write A Custom Renderer For iOS
Custom renderers are used when you want to override the default behaviour for a particular UI component on a particular platform. In this case we are overriding the default renderer for our IconLabel class to use the font we have just imported.

Add an empty class file to your iOS project and edit like so…

using System;
using UIKit;

using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

// This informs the compiler that we're using this class to render an IconLabel on this platform
[assembly: ExportRenderer (typeof (CustomFontDemo.IconLabel), typeof (CustomFontDemo.iOS.IconLabelRenderer))]

namespace CustomFontDemo.iOS
{
	public class IconLabelRenderer : LabelRenderer
	{
		public IconLabelRenderer ()
		{
		}

		// sets the font for the platform-specific ui component to be our custom font
		protected override void OnElementChanged (ElementChangedEventArgs e)
		{
			base.OnElementChanged (e);

			double? fs = e.NewElement?.FontSize;
			// Note we're using the font family name here, NOT the filename
			UIFont font = UIFont.FromName ("Material Icons", (int)fs);
			Control.Font = font;
		}

		// Without this strange things happen if you update the text after the label is first displayed
		protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			base.OnElementPropertyChanged (sender, e);
			if (e.PropertyName.Equals ("Text"))
			{
				Label label = sender as Label;
				// Note we're using the font family name here, NOT the filename
				UIFont font = UIFont.FromName ("Material Icons", (int)label.FontSize);
				Control.Font = font;
			}
		}
	}
}

4. Set The Text To The Appropriate Icon
Any custom icon font should come with documentation showing which icon maps to which unicode text character. Usually this is done with HTML-encoded values. For the purposes of this demo I’m going to use the value for the ‘favourites’ icon in Google’s Material Icons font. I can reference this directly in the XAML like so, note I’ve alse increased the font size for the label here too!

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
			 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
			 xmlns:local="clr-namespace:CustomFontDemo" 
			 x:Class="CustomFontDemo.CustomFontDemoPage">
	<local:IconLabel Text="&#xE87D;" VerticalOptions="Center" HorizontalOptions="Center" FontSize="144"/>
</ContentPage>

Alternatively you can set the text in code using the WebUtility.HtmlDecode() method in the System.Net namespace.

Text = System.Net.WebUtility.HtmlDecode ("&#xE87D;");

Running the above code should give you a label displaying an icon from your custom icon font in iOS. To get things running in Android we have a similar (though slightly different) procedure.

5. Import Your Custom Font Into Your Android Project
Right-click on the Assets directory in your Android project and select ‘Add Files’. Navigate to the font file you have chosen on your hard drive and add it to the project. Once the file has been added right-click on it and check the ‘Build Action’ is set to ‘AndroidAsset’.

mockup_3x
Where To Import The Font File On Android

6. Write A Custom Renderer For Android
Add an empty class file to your Android project and edit like so…

UPDATE: I have discovered that using Typeface.CreateFromAsset in this way is extremely inefficient and can lead to serious performance issues when using a lot of labels etc. I have left the code as it is for the sake of clarity but really you should only initiate your typeface once (I do it in a static class). I would have presumed this type of caching would have been handled at the OS level but apparently not!

using System;
using Android.Graphics;

using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

// This informs the compiler that we're using this class to render an IconLabel on this platform
[assembly: ExportRenderer (typeof (CustomFontDemo.IconLabel), typeof (CustomFontDemo.Droid.IconLabelRenderer))]

namespace CustomFontDemo.Droid
{
	public class IconLabelRenderer : LabelRenderer
	{
		public IconLabelRenderer ()
		{
		}

		// sets the font for the platform-specific ui component to be our custom font
		protected override void OnElementChanged (ElementChangedEventArgs e)
		{
			base.OnElementChanged (e);
			// Note we're using the filename here, NOT the font family name
			var font = Typeface.CreateFromAsset (Forms.Context.ApplicationContext.Assets, "materialicons.ttf");
			Control.Typeface = font;
		}
	}
}

And that should be job done – you now have scaleable vector-based icons using a custom font running on both iOS and Android in Xamarin.Forms!

You can download a .zip file for the project here.

mockup_3x
Custom Icon Font Running On Xamarin.Forms iOS

mockup_3x
Custom Icon Font Running On Xamarin.Forms Android
Advertisements

5 Comments

  1. DEEPAK SAKPAL
    Posted August 29, 2017 at 6:17 pm | Permalink | Reply

    Hi.

    Nice article but I’m facing an issue during implementation.

    When I change the label text at runtime, it shows the actual Unicode text rather than the font icon. any idea why?

    Thanks

    • Posted August 29, 2017 at 6:36 pm | Permalink | Reply

      Is this on Android? Try overriding OnElementPropertyChanged() to reset the font as well (as the iOS renderer does). I think I found I had to do this on Android as well in some circumstances.

      • DEEPAK SAKPAL
        Posted August 29, 2017 at 8:01 pm | Permalink

        Yes, that was on Android. The issue is fixed using following code which you already told in your article but somehow I missed it.

        Text = System.Net.WebUtility.HtmlDecode (“”);

        Now, I’m running into another problem but this time on iOS. I’m getting following exception at line Control.Font = font;

        Exception:
        System.ArgumentNullException has been thrown
        Value cannot be null.
        Parameter name: value

        Looks like the font is not loading? I’m doing exactly as you told. I can share the sample project I’m currently working on if needed.

      • DEEPAK SAKPAL
        Posted August 29, 2017 at 8:23 pm | Permalink

        Fixed the iOS issue as well. The culprit was UiAppFonts. It should be UIAppFonts. Notice the capital I 🙂

      • Posted August 29, 2017 at 8:55 pm | Permalink

        Good spot mate and thanks for posting your solution. I will update the article.

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

%d bloggers like this: