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.
Problem
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!
Xaml
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.
Simple Renderer
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 FontFamily
, appends -Regular.ttf
and loads this font from the assets.
Result
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.
FormattedText
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 SpannableString
using FormattedStringExtensions.ToAttributed
which is basically iterating the spans and creating FontSpan
s 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
FontSpan
is a private inner class ofFormattedStringExtensions
that therefore you can’t inherit from and override some functions.
Extended Renderer
In our extended renderer, we need to replace all FontSpan
with our CustomTypefaceSpan
. The UpdateFormattedText
function does exactly this and gets called in OnElementChanged
and OnElementPropertyChanged
of our renderer.
Result
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:
FontAttibutes
are not usedSpan
can’t handle custom fonts if theSize
orFontAttributes
are set and theFontFamily
has 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-Bold
and 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.
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.
Our UpdateFormattedText
method is casting the AttributedText property of our UILabel
to 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.
Result
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 \n
occur.
Result
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.
Conclusion
Notes
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.
Code Sample
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.
http://stackoverflow.com/a/36236651/1489968
Thanks!
Background Photo by Karen / CC BY
Found a typo? Send me a pull request!