1
votes

I have followed Sven's example for custom font for formattedstrings here: https://github.com/smstuebe/xamarin-forms-formattedtext

However, I'm running into a strange issue where the tap gestures on my view don't work if I implement the UIFormattedStringLabel(). This is super strange because if I use a regular label that does not use the custom renderer provided, the gestures are detected, the formatted string is displayed (just the fonts are default) and everything loads correctly.

So, I think there's an issue with setting up the renderer above as the problem seems to stem from that. Perhaps, I am missing a step for what I am trying to do.

using Android.Graphics;
using Android.Text;
using Android.Text.Style;
using Android.Util;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

namespace Proj.Droid.CustomRenderer
{
    public class CustomTypefaceSpan : MetricAffectingSpan
    {
        private readonly Typeface _typeFace;
        private readonly TextView _textView;
        private Font _font;

        public CustomTypefaceSpan(TextView textView, Label label, Font font)
        {
            _textView = textView;
            _font = font;
            _typeFace = Typeface.CreateFromAsset(Forms.Context.Assets, GetFontName(_font.FontFamily ?? label.FontFamily, _font.FontAttributes));
        }

        private static string GetFontName(string fontFamily, FontAttributes fontAttributes)
        {
            var postfix = "Regular";
            var bold = fontAttributes.HasFlag(FontAttributes.Bold);
            var italic = fontAttributes.HasFlag(FontAttributes.Italic);
            if (bold && italic) { postfix = "BoldItalic"; }
            else if (bold) { postfix = "Bold"; }
            else if (italic) { postfix = "Italic"; }

            return $"{fontFamily}-{postfix}.otf";
        }

        public override void UpdateDrawState(TextPaint paint)
        {
            ApplyCustomTypeFace(paint);
        }

        public override void UpdateMeasureState(TextPaint paint)
        {
            ApplyCustomTypeFace(paint);
        }

        private void ApplyCustomTypeFace(Paint paint)
        {
            paint.SetTypeface(_typeFace);
            paint.TextSize = TypedValue.ApplyDimension(ComplexUnitType.Sp, _font.ToScaledPixel(), _textView.Resources.DisplayMetrics);
        }
    }
}

using System.ComponentModel;
using System.Reflection;
using Android.Graphics;
using Android.Text;
using Java.Lang;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Proj.Droid.CustomRenderer;
using Proj.Core.UI.XamarinForms.Controls;
using System;

[assembly: ExportRenderer(typeof(UIFormattedStringLabel), typeof(FormattedLabelRenderer))]
namespace Proj.Droid.CustomRenderer
{
    public class SimpleLabelRenderer : LabelRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            base.OnElementChanged(e);
            if (Control != null)
            {
                Control.Typeface = Typeface.CreateFromAsset(Forms.Context.Assets, GetFontName(Element.FontFamily, Element.FontAttributes));
            }
        }

        private static string GetFontName(string fontFamily, FontAttributes fontAttributes)
        {
            var postfix = "Regular";
            var bold = fontAttributes.HasFlag(FontAttributes.Bold);
            var italic = fontAttributes.HasFlag(FontAttributes.Italic);
            if (bold && italic) { postfix = "BoldItalic"; }
            else if (bold) { postfix = "Bold"; }
            else if (italic) { postfix = "Italic"; }

            return $"{fontFamily}-{postfix}.otf";
        }
    }



     public class FormattedLabelRenderer : SimpleLabelRenderer
        {
            protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
            {
                base.OnElementChanged(e);
                UpdateFormattedText();
            }

            private void UpdateFormattedText()
            {
                if (Element?.FormattedText == null)
                    return;

                var extensionType = typeof(FormattedStringExtensions);
                var type = extensionType.GetNestedType("FontSpan", BindingFlags.NonPublic);
                var ss = new SpannableString(Control.TextFormatted);
                var spans = ss.GetSpans(0, ss.ToString().Length, Class.FromType(type));
                foreach (var span in spans)
                {
                    var start = ss.GetSpanStart(span);
                    var end = ss.GetSpanEnd(span);
                    var flags = ss.GetSpanFlags(span);
                    var font = (Font)type.GetProperty("Font").GetValue(span, null);
                    ss.RemoveSpan(span);
                    var newSpan = new CustomTypefaceSpan(Control, Element, font);
                    ss.SetSpan(newSpan, start, end, flags);
                }
                Control.TextFormatted = ss;
            }

            protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                base.OnElementPropertyChanged(sender, e);

                if (e.PropertyName == Label.FormattedTextProperty.PropertyName ||
                    e.PropertyName == Label.TextProperty.PropertyName ||
                    e.PropertyName == Label.FontAttributesProperty.PropertyName ||
                    e.PropertyName == Label.FontProperty.PropertyName ||
                    e.PropertyName == Label.FontSizeProperty.PropertyName ||
                    e.PropertyName == Label.FontFamilyProperty.PropertyName ||
                    e.PropertyName == Label.TextColorProperty.PropertyName)
                {
                    UpdateFormattedText();
                }
            }
        }
    }

using System;
using Xamarin.Forms;
using XLabs.Forms.Controls;

namespace Proj.Core.UI.XamarinForms.Controls
{

    public class UIFormattedStringLabel : Label
    {
        public UIFormattedStringLabel()
        {

        }
    }
}

label code:

var formatString = new FormattedString();

formatString.Spans.Add(new Span { Text = Time.Text + "\n", FontAttributes = FontAttributes.Bold, ForegroundColor = ColorHelper.FromHex(CoreTheme.COLOR_DARK_GREY)});
formatString.Spans.Add(new Span { Text = TimeRemaining.Text, FontAttributes = FontAttributes.Bold,ForegroundColor = ColorHelper.FromHex(CoreTheme.COLOR_DEFAULT_BLACK)});

labelTime = new UIFormattedStringLabel(); 
labelTime.ClassId = offerid.ToString();
labelTime.WidthRequest = (DeviceDisplaySettings.defaultwidth / buttonsToShow) - 10;
labelTime.HeightRequest = 40;
labelTime.VerticalTextAlignment = TextAlignment.Center;
labelTime.BackgroundColor = ColorHelper.FromHex(CoreTheme.COLOR_LIGHT_GREY);
labelTime.FormattedText = formatString; 
2
See my blogpost and the linked github repository smstuebe.de/2016/04/03/formattedtext.xamrin.forms You don't have to follow the steps. It's already implemented in the repository :) (linked on the bottom)Sven-Michael Stübe
@Sven-MichaelStübe, thank you so much for your help again. Just curious, so I set the typeface manually in my formattedstring using this custom renderer and not within the custom renderer right? Thanks!Kala J
Yes. Just have a look at the XAML code ;)Sven-Michael Stübe
@Sven-MichaelStübe. I have a question. So the only thing I changed in your code is the extension from .ttf to .otf and I am deriving a custom renderer for a custom control. I noticed that if I use a custom control for the code in the repo, there's an issue with it where taps are not recognized on the view. If I use my old button, the view is setup properly. Have you tried to add your renderer to a custom control like a custom label?Kala J
No I haven't tried. I can't imagine that this will cause any problems.Sven-Michael Stübe

2 Answers

2
votes

As mentioned in my blogpost, the Renderer isn't perfect. In your case it will crash, if the FontFamily of the Label is null and the FontFamily of the Span is null.

I added some fixes for this to the repository. https://github.com/smstuebe/xamarin-forms-formattedtext/commit/d3b9eab7f588917f1e4417188a12e66f97cf1081

UpdateFormattedText

We only replace the span, if we have a font set.

private void UpdateFormattedText()
{
    if (Element?.FormattedText == null)
        return;

    var extensionType = typeof(FormattedStringExtensions);
    var type = extensionType.GetNestedType("FontSpan", BindingFlags.NonPublic);
    var ss = new SpannableString(Control.TextFormatted);
    var spans = ss.GetSpans(0, ss.ToString().Length, Class.FromType(type));
    foreach (var span in spans)
    {
        var font = (Font)type.GetProperty("Font").GetValue(span, null);
        if ((font.FontFamily ?? Element.FontFamily) != null)
        {
            var start = ss.GetSpanStart(span);
            var end = ss.GetSpanEnd(span);
            var flags = ss.GetSpanFlags(span);
            ss.RemoveSpan(span);
            var newSpan = new CustomTypefaceSpan(Control, Element, font);
            ss.SetSpan(newSpan, start, end, flags);
        }
    }
    Control.TextFormatted = ss;
}

I don't check for null in CustomTypefaceSpan.ApplyCustomTypeFace(Paint paint), because it will show you that your font got not loaded correctly, and you have to double check the names and the assets.

0
votes

Typeface is set using the SetTypeface method. Changing Renderer as below might fix it

class FormattedStringLabelRender : LabelRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
    {
        base.OnElementChanged(e);
        var label = (TextView)Control; // for example
        Typeface font = Typeface.CreateFromAsset(Forms.Context.Assets, "Fonts/ProximaNova-Bold.otf");  // font name specified here
        label.SetTypeface (font, TypefaceStyle.Normal);
    }
}