Reputation: 2070
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;
Upvotes: 1
Views: 1012
Reputation: 14750
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.
Upvotes: 2
Reputation: 2434
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);
}
}
Upvotes: 0