Unable to clear TextDecoration in Xamarin Forms UWP App - xamarin

Using Xamarin Forms (version 2.5.0.121934), I'm working on an app targeting Android, iOS, and UWP. I need to add underlining and strikethrough to some text, which require custom renderers. For Android and iOS, everything is working fine, and on UWP, applying strikethrough or underline works correctly, but removing those decorations isn't working.
Here's the entirety of the UWP renderer:
[assembly: ExportRenderer(typeof(EnhancedLabel), typeof(EnhancedLabelRenderer))]
namespace myApp.UWP
{
public class EnhancedLabelRenderer : LabelRenderer
{
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var strikethrough = ((EnhancedLabel)sender).Strikethrough;
var underline = ((EnhancedLabel)sender).Underline;
if (strikethrough && underline)
{
Control.TextDecorations = TextDecorations.Strikethrough | TextDecorations.Underline;
}
else if (strikethrough)
{
Control.TextDecorations = TextDecorations.Strikethrough;
}
else if (underline)
{
Control.TextDecorations = TextDecorations.Underline;
}
else
{
Control.TextDecorations = TextDecorations.None;
}
}
}
}
EnhancedLabel is a simple class that extends Xamarin.Forms.Label and adds the simple BindableProperty fields that specify strikethrough or underlining.
The renderer is properly setting TextDecorations.None, but that isn't having an effect on the UI. I've worked through this in the debugger, and can actually see that the state of the TextBlock within the ExtendedLabel has TextDecorations.None, but the UI is still drawing it with underlining or strikethrough (essentially, either of those can be added, but neither can be removed).
I've gone through the Xamarin documentation and looked at the bugs in Bugzilla, and haven't found any clues. Has any one else encountered this? Wondering if there's a UWP-specific call I need to make that I missed, or if using TextDecorations is the wrong way to apply the styles, or if I've actually stumbled across a bug.

Bug in UWP as in Xaml below:
<TextBlock>
<Run Text="Decorations can be toggled on and off"/>
</TextBlock>
<TextBlock Text="Decorations will not toggle off"/>
It is the same issue if you code the TextBlock:
TextBlock textBlock = new TextBlock { FontSize = 18.0 };
textBlock.Inlines.Add(new Windows.UI.Xaml.Documents.Run { Text = "This text will not stick on text decoration." });
TextBlock textBlockBad = new TextBlock
{
FontSize = 18.0,
Text = "This text will not enable the TextDecorations to be turned off"
};
Same behaviour found with Typography.Capitals
Just need to use only Inlines for TextBlocks and presumably RichTextBlocks to avoid these issues.

Wondering if there's a UWP-specific call I need to make that I missed, or if using TextDecorations is the wrong way to apply the styles, or if I've actually stumbled across a bug.
If you want yo use TextDecorations, you could use the Run instance to pack the decorated text like the follow.
Underline ul = new Underline();
ul.TextDecorations = TextDecorations.Strikethrough;
Run r = new Run();
r.Text = "Here is an underlined text";
ul.Inlines.Add(r);
MyTextBlock.Inlines.Add(ul);
For you requirement, I have create a CustomLabel that you could use directly.
CustomLabel.cs
public class CustomLabel : Label
{
public static readonly BindableProperty DeckProperty = BindableProperty.Create(
propertyName: "Deck",
returnType: typeof(TextDeck),
declaringType: typeof(CustomLabel),
defaultValue: default(TextDeck));
public TextDeck Deck
{
get { return (TextDeck) GetValue(DeckProperty); }
set { SetValue(DeckProperty, value); }
}
}
public enum TextDeck
{
None = 0,
//
// Summary:
// Underline is applied to the text.
Underline = 1,
//
// Summary:
// Strikethrough is applied to the text.
Strikethrough = 2
}
CustomLabelRenderer.cs
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var element = Element as CustomLabel;
var underline = new Underline();
var run = new Run();
switch (element.Deck)
{
case TextDeck.None:
underline.TextDecorations = TextDecorations.None;
break;
case TextDeck.Strikethrough:
underline.TextDecorations = TextDecorations.Strikethrough;
break;
case TextDeck.Underline:
underline.TextDecorations = TextDecorations.Underline;
break;
}
run.Text = element.Text;
underline.Inlines.Add(run);
Control.Inlines.Clear();
Control.Inlines.Add(underline);
}
}
}
Usage
<local:CustomLabel Deck="Underline" Text="Welcome to Xamarin.Forms!" />

Related

Placeholder Margin on Xamarin Forms Editor using Custom Renderer

I've an Editor on a page in my Xamarin Forms Project and I'm looking to amend the placeholder text position by adding a slight margin.
I was able to add a margin to the text the user types (but not the placeholder) using "TextContainerInset" in my iOS Custom renderer.
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
this.Control.InputAccessoryView = null;
this.Control.Layer.CornerRadius = 10;
this.Control.Layer.BorderColor = UIColor.LightGray.CGColor;
this.Control.Layer.BorderWidth = (nfloat)0.5;
this.Control.TextContainerInset = new UIEdgeInsets(15,15,15,15);
}
However, this inset doesn't apply to the placeholder position.
Is there a way to move the placeholder position using the custom renderer?
You could set the padding like:
public class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged (e);
if (Control != null) {
Control.LeftView = new UIView(new CGRect(0,0,15,0));
Control.LeftViewMode = UITextFieldViewMode.Always;
Control.RightView = new UIView(new CGRect(0, 0, 15, 0));
Control.RightViewMode = UITextFieldViewMode.Always;
}
}
}
Workaournd for editor:
warp the editor in the frame and set the padding like:
<StackLayout Margin="20">
<Frame CornerRadius="10" BorderColor="Gray" HeightRequest="50" Padding="5,10,5,10" HasShadow="False">
<Editor Placeholder="Enter the editor text" ></Editor>
</Frame>
</StackLayout>
The underlying UITextView does not implement the Placeholder. However if you look at the EditorRenderer code which derives from UITextView, Xamarin has implemented the Placeholder there.
private UILabel _placeholderLabel;
By using reflection, I can get a pointer to the UILabel _placeholderLabel, but I can't for the life of me move it.
If someone can help me move the UILabel, I believe we have the answer!
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (Control != null)
{
editor = e.NewElement as EditorEx;
//Use reflection to access private placeholder in parent
//https://www.c-sharpcorner.com/UploadFile/6f0898/how-to-access-a-private-member-of-a-class-from-other-class/
System.Reflection.FieldInfo receivedObject = typeof(EditorRenderer).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[1];
_placeholderLabel = receivedObject.GetValue(this) as UILabel;
_placeholderLabel.Text = "move me!!; //this works
_placeholderLabel.BackgroundColor = UIColor.Green; //this works
//put it on another message to see if that solves it (it doesn't)
Device.BeginInvokeOnMainThread(() =>
{
//HELP NEEDED HERE!!!
//_placeholderLabel.Frame = new CGRect(-15, -10, 500, 30); //doesn't work
//_placeholderLabel.Bounds = new CGRect(-15, -10, 500, 30); //doesn't work
_placeholderLabel.LayoutMargins = new UIEdgeInsets(-15, -10, 5, 5); //doesn't work
});
}
}

FontAwesome icon with normal text in Xamarin

I want to make a button that has a small icon (from FontAwesome) and text on it in my Xamarin app. I know I can just make a button but the problem is that I will require two fonts (the standard font for text and FontAwesome for the icon). Would anyone happen to know how I can do this, or if there is another way to achieve what I want?
Thanks!
As the json mentioned, I just made a simple implementation.
Create a new class inherit from Label, set FormattedText to combine the string(standard and icon), and add tap gesture on it .
public class MyLabel : Label
{
public delegate void MyHandler(object sender, EventArgs e);
public event MyHandler myEvent;
public MyLabel(string _myIcon, string _myText)
{
//build the UI
FormattedString text = new FormattedString();
text.Spans.Add(new Span { Text = _myIcon ,FontFamily= "FontAwesome5Free-Regular" });
text.Spans.Add(new Span { Text = _myText, TextColor = Color.Red ,BackgroundColor = Color.Blue });
FormattedText = text;
//tap event
TapGestureRecognizer tap = new TapGestureRecognizer();
tap.Tapped += (sender,e) => {
myEvent(sender,e);
};
}
}
Usage
MyLabel label = new MyLabel("", "test");
label.myEvent += (sener,e) =>
{
//do something when tapping
};
Content = label;
For how to integrate FontAwesome in Xamarin.Forms ,refer to
https://montemagno.com/xamarin-forms-custom-fonts-everywhere/.

Bug - TextDecoration Strikethrough not working on iOS

I have a label where I want strikethrough and a lineheight of 1.5
This works fine with Android, but strikethrough does not work on iOS when LineHeight is also set.
Steps to Reproduce
- Create new Xamarin.Forms project.
- Add label with textdecoration=strikethrough
- Test strikethrough
- Run emulator
Expected Behavior
Display of label with Strikethrough text
Actual Behavior
Text displayed normally, not with Strikethrough on iOS (Android works
fine)
I resolved this issue with a label custom renderer...
[assembly: ExportRenderer(typeof(Label), typeof(CustomLabelRenderer))]
namespace YourApp.iOS.Renderers
{
public class CustomLabelRenderer : LabelRenderer
{
private readonly IDictionary<TextDecorations, NSString> attributeNames =
new Dictionary<TextDecorations, NSString> {
{
TextDecorations.Underline,
UIStringAttributeKey.UnderlineStyle },
{
TextDecorations.Strikethrough,
UIStringAttributeKey.StrikethroughStyle
}
};
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (sender is Label label &&
label.FormattedText?.Spans.ToList() is List<Span> spans)
{
var aux = new NSMutableAttributedString(Control.AttributedText);
spans.ForEach(span => {
if (attributeNames.TryGetValue(span.TextDecorations,
out var attributeName))
{
var startIndex = Control.Text.IndexOf(span.Text);
aux.AddAttribute(
attributeName,
new NSNumber(1),
new NSRange(startIndex, span.Text.Length));
}
});
Control.AttributedText = aux;
}
}
}
}
This works for Underline and Strikethrough.

How to hide clear button Icon inside SearchBar control Xamarin forms

I am using xamarin forms SearchBar control. I want to remove clear button x icon without using custom renderer.
<controls:ExSearchBar
x:Name="entrySearch"
BackgroundColor="White"
CornerRadius="6"
BorderWidth="1"
HeightRequest="45"
Text="{Binding SearchText}"
HorizontalOptions="FillAndExpand"
Placeholder="search">
</controls:ExSearchBar>
This is ExSearchBar control in shared project
public class ExSearchBar : SearchBar
{
public static readonly BindableProperty ElevationProperty = BindableProperty.Create(nameof(Elevation), typeof(float), typeof(ExFrame), default(float));
public float Elevation
{
get { return (float)GetValue(ElevationProperty); }
set { SetValue(ElevationProperty, value); }
}
}
How can I do that?
The situation you are describing is the exact reason why Xamarin Forms ships with the ability to create custom renderers. The forms team define the UI elements in abstract (seperate from their native implementation) and when there is a specific feature that is not defined in their API, you must go down to the platform level to change it.
You can also use an Effect to achieve the same result, I have provided a custom renderer for iOS & Android to show you how you would go about achieving the UI you desire:
iOS:
public class SearchBarButtonRenderer : SearchBarRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Control.SearchTextField.ClearButtonMode = UITextFieldViewMode.Never;
}
}
}
Really simple, just remove the clear button from the underlying UITextField
Android
public class SearchBarButtonRenderer : SearchBarRenderer
{
private readonly Context _context;
public SearchBarButtonRenderer(Context context)
: base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
// Get Search Close Button Drawable
var closeButtonId = Resources.GetIdentifier("android:id/search_close_btn", null, null);
var searchEditText = Control.FindViewById<ImageView>(closeButtonId);
// Get Close Button Drawable To Replace Existing Drawable
var closeDrawable = GetCloseButtonDrawable() as VectorDrawable;
if (closeDrawable is null) return;
// Apply Transparent Color To Drawable (To Make Invisible)
var buttonColor = Xamarin.Forms.Color.Transparent.ToAndroid();
closeDrawable.SetTint(buttonColor);
// Set Drawable On Control
searchEditText.SetImageDrawable(closeDrawable);
}
}
private Drawable GetCloseButtonDrawable()
{
return ContextCompat.GetDrawable(_context, Resource.Drawable.abc_ic_clear_material);
}
}
A little bit of a fiddle, find the close button drawable and replace it with a custom styled drawable

Custom Font in Xamarin.Forms Label with FormattedString

I have created a custom LabelRenderer in my Android app to apply a custom font in a Xamarin Android app (https://developer.xamarin.com/guides/xamarin-forms/user-interface/text/fonts/).
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.
Anyone have success doing this? An option, since I'm just stacking lines of different sized text, is to use separate label controls for each, but I'd prefer to use a formatted string if possible.
Here's the guts of my custom renderer:
[assembly: ExportRenderer (typeof (gbrLabel), typeof (gbrLabelRenderer))]
public class gbrLabelRenderer: LabelRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Label> e)
{
base.OnElementChanged (e);
var label = (TextView)Control;
Typeface font = Typeface.CreateFromAsset (Forms.Context.Assets, "Lobster-Regular.ttf");
label.Typeface = font;
}
}
And here's my simple label control... all it does is apply the font to iOS, and leaves applying the font for Android up to the custom renderer.
public class gbrLabel: Label
{
public gbrLabel ()
{
Device.OnPlatform (
iOS: () => {
FontFamily = "Lobster-Regular";
FontSize = Device.GetNamedSize(NamedSize.Medium,this);
}
}
}
Works fine for labels with just the .Text property... but not for labels with the .FormattedText property.
Should I keep digging, or just stack my labels since that's an option in this case?
Here's an example of the various ways I've tried this in the Formatted text, since that was requested:
var fs = new FormattedString ();
fs.Spans.Add (new Span {
Text = string.Format("LINE 1\n",Title),
FontSize = Device.GetNamedSize(NamedSize.Large,typeof(Label))
});
fs.Spans.Add (new Span {
Text = string.Format ("LINE 2\n"),
FontSize = Device.GetNamedSize(NamedSize.Large,typeof(Label)) * 2,
FontAttributes = FontAttributes.Bold,
FontFamily = "Lobster-Regular"
});
fs.Spans.Add (new Span {
Text = string.Format ("LINE 3\n"),
FontSize = Device.GetNamedSize(NamedSize.Medium,typeof(Label)),
FontFamily = "Lobster-Regular.ttf"
});
gbrLabel lblContent = new gbrLabel {
FormattedText = fs
}
None of these (the first should be set by the default class / renderer, and the second 2 are variations of including the font in a span definition itself) work on Android.
Note: Android and iOS issues have been summarized on a blog post: smstuebe.de/2016/04/03/formattedtext.xamrin.forms/
The font is set as long as you do not set FontSize or FontAttributes. So I had the look at the implementation and found that the FormattedText is trying to load the font like the default renderer which doesn't work on Android.
The android formatting system works very similar to that one of Xamarin.Forms. It's using spans to define text attributes. The renderer is adding a FontSpan for every Span with a custom font, size or attribute. Unfortunately, the FontSpanclass is a private inner class of FormattedStringExtensions so we have to deal with reflections.
Our Renderer is updating the Control.TextFormatted on initialization and when the FormattedText property changes. In the update method, we get all FontSpans and replace them with our CustomTypefaceSpan.
Renderer
public class FormattedLabelRenderer : LabelRenderer
{
private static readonly Typeface Font = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Regular.ttf");
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
Control.Typeface = Font;
UpdateFormattedText();
}
private void UpdateFormattedText()
{
if (Element.FormattedText != null)
{
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, 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)
{
UpdateFormattedText();
}
}
}
I'm not sure, why you introduced a new element type gbrLabel, but as long as you only wan't to change the renderer, you don't have to create a custom element. You can replace the renderer of the default element:
[assembly: ExportRenderer(typeof(Label), typeof(FormattedLabelRenderer))]
CustomTypefaceSpan
public class CustomTypefaceSpan : MetricAffectingSpan
{
private readonly Typeface _typeFace;
private readonly Typeface _typeFaceBold;
private readonly Typeface _typeFaceItalic;
private readonly Typeface _typeFaceBoldItalic;
private readonly TextView _textView;
private Font _font;
public CustomTypefaceSpan(TextView textView, Font font)
{
_textView = textView;
_font = font;
// Note: we are ignoring _font.FontFamily (but thats easy to change)
_typeFace = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Regular.ttf");
_typeFaceBold = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Bold.ttf");
_typeFaceItalic = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Italic.ttf");
_typeFaceBoldItalic = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-BoldItalic.ttf");
}
public override void UpdateDrawState(TextPaint paint)
{
ApplyCustomTypeFace(paint);
}
public override void UpdateMeasureState(TextPaint paint)
{
ApplyCustomTypeFace(paint);
}
private void ApplyCustomTypeFace(Paint paint)
{
var tf = _typeFace;
if (_font.FontAttributes.HasFlag(FontAttributes.Bold) && _font.FontAttributes.HasFlag(FontAttributes.Italic))
{
tf = _typeFaceBoldItalic;
}
else if (_font.FontAttributes.HasFlag(FontAttributes.Bold))
{
tf = _typeFaceBold;
}
else if (_font.FontAttributes.HasFlag(FontAttributes.Italic))
{
tf = _typeFaceItalic;
}
paint.SetTypeface(tf);
paint.TextSize = TypedValue.ApplyDimension(ComplexUnitType.Sp, _font.ToScaledPixel(), _textView.Resources.DisplayMetrics);
}
}
Our Custom CustomTypefaceSpanis similar to the FontSpan of Xamarin.Forms, but is loading the custom fonts and can load different fonts for different FontAttributes.
The result is a nice colorful Text :)

Resources