Last weekend I saw Chet’s question on stackoverflow and thought it would be easy to answer. But then it took me several hours to investigate what the problem is. I want to share my findings and solutions additionally to my stackoverflow answer here on my blog.
Let chet describe the problem
I have created a custom LabelRenderer in my Android app to apply a custom font in a Xamarin Android app. Everything works great for a normal label with the content added to the .Text property. However, if I create a label using .FormattedText property, the custom font is not applied.
Well, let's visualize it!
We use this view that has 3 labels with different attributes and a custom font and one label, that has a custom font and a
FormattedText with several differently styled lines.
As described in Setting Fonts in Xamarin.Forms we need a custom renderer if we want to use custom fonts in Android. I used the a simple custom renderer (like Chet did) for the first try. It uses the specified
-Regular.ttf and loads this font from the assets.
As we see, the custom font is applied only to the simple labels, but the bold and italic attribute is not considered. You might facepalm now and think “Sven! You are loading the regular font in your custom renderer!”. That’s true. I did this on purpose, because its that one from Xamarin’s blog post. So let’s fix it with adding this method to our renderer.
Yay, at least, the normal labels are looking as expected, now.
Why don’t you set the font family to “XYZ-Bold” if you want bold text?
Because I’d expect it to work similar to standard fonts. As you can see on the 3rd last line, the attributes take effect on the standard font.
Ok, basics done, let’s finally answer Chet’s question! As you can see, I set the font on the formatted text label but it isn’t applied to the spans. Unfortunately, there is no
SpanRenderer that we can override. But how can we customize the interpretation of a span then!? Android is using a very similar method to allow formatted strings within a label. The sections are even called spans! We just have to find out how Xamarin.Forms translates them.
Decompile to the rescue!
I used JetBrains’ dotPeek to gather some insight. The
LabelRenderer is converting the FormattedString into a
FormattedStringExtensions.ToAttributed which is basically iterating the spans and creating
FontSpans and setting them on the right positions. Ok, there are unfortunately two problems:
- You can’t override something, because the functionality is in an extension method
FontSpanis a private inner class of
FormattedStringExtensionsthat therefore you can’t inherit from and override some functions.
In our extended renderer, we need to replace all
FontSpan with our
UpdateFormattedText function does exactly this and gets called in
OnElementPropertyChanged of our renderer.
iOS works ... NOT
I thought it would be nice to sum up my findings in a neat blog post. For a comparsion I wanted to show Android and iOS side by side to visualize the error. But unfortunately after I started the iOS app I saw the following:
As you can see, the standard iOS LabelRenderer works better than the Android one. But its not perfect, because:
FontAttibutesare not used
Spancan’t handle custom fonts if the
FontAttributesare set and the
FontFamilyhas not been set explicitly
- the background is drawn to the end of line, if you have line breaks in your text
You can fix the first issue, by setting
FontFamily explicitly to
LobsterTwo-Italic. But as mentioned in the Android part I think it would be very inconsistent. The same applies to the second issue. You can simply set the
FontFamily explicitly on each span and you are done. The third issue can be solved, by moving the line break to non formatted spans. That’s alot of manual work to do. Let’s fix this in a custom renderer.
iOS is using
AttributedString to format text. The standard
LabelRenderer is generating such an
AttributedString out of the spans. We can use the same approach as in the Android renderer. After the standard renderer has done his work, we correct his work a little bit.
UpdateFormattedText method is casting the AttributedText property of our
NSMutableAttributedString. This is - as the name implies - the mutable version of
AttributedString :) It is then calling
FixFontAtLocation once, if
FormattedString isn’t used, or for each span, if
FormattedString is used.
FixFontAtLocation gets the font attribute at the given location and replaces it with a font attribute that has the correct font family.
Fixing the Background
With the knowledge of the previous section, fixing the backgorund is easy. We just have to write a function that removes the background color at the positions where
And what we get looks pretty similar to our Android version. The Android default font color is grey, because this is set in the used theme.
The Renderers implement a behaviour that is similar to not using custom fonts, but aren’t perfect. In productive projects you should
- cache the fonts
- add some fall back mechanisms if no font for Bold, Italic and BoldItalic are available
Some fonts have more than 2 Attributes. With the gathered knowledge it is now really easy to implement a
CustomLabel with some sort of
FontAttributesEx property where you can define Bold, Italic, Thin, etc.
The actual implementation of the Renderers and the sample projects are pushed in a github project (xamarin-forms-formattedtext). Feel free to use these if you want to play around or have a closer look to the formatted text issue.