** UPDATE **
We expanded this and added an Effect for eveyone to use in the Xamarin Forms Community Toolkit!
It’s available here : https://github.com/FormsCommunityToolkit/FormsCommunityToolkit
The effect itself resides here : https://github.com/FormsCommunityToolkit/FormsCommunityToolkit/tree/dev/src/Effects/Effects/Label
On a project I’m working on, we needed a label that would show an ellipsis at the end. ( the 3 … indicating there is more text but not enough space on the screen to fit it all )
Having this on a label control in Xamarin forms is easy, you just add the property
1 |
<Label LineBreakMode="TailTruncation" /> |
this will force the ellipsis to appear if needed.
But on a specific page we wanted to show more text to the end user and even than add an ellipsis if needed. So in other words we would love to be able to tell the label control how many lines it should at least try to display.
To get this working in Xamarin forms you’ll need to add a custom renderer. Because the Xamarin forms label control doesn’t have any property available for us to manipulate to accomplish this.
This is not difficult at all to do, but there is a small gotcha with Android when you want to pull this off!
But let’s start with what you need to do to get this working.
In the Xamarin forms PCL ( so the general one, not the iOS or Android project ), we first add a class called MultiLineLabel.cs – this will be our own custom control.
It inherits from Label and we only need to add 1 dependency property called Lines, defined as an int. It looks like this:
1 2 3 4 5 6 7 8 9 10 11 |
public class MultiLineLabel : Label { private static int _defaultLineSetting = -1; public static readonly BindableProperty LinesProperty = BindableProperty.Create(nameof(Lines), typeof(int), typeof(MultiLineLabel), _defaultLineSetting); public int Lines { get { return (int)GetValue(LinesProperty); } set { SetValue(LinesProperty, value); } } } |
After defining this custom control, we can use it on our XAML page, like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<StackLayout> <Label Text="NORMAL" FontSize="12" FontAttributes="Bold" /> <Label Text="Welcome to Xamarin Forms! Let us try to create a label with text that is too long to fit on 1 line so it should wrap and implement an ellipsis if it's more than 2 lines in total" VerticalOptions="Center" HorizontalOptions="Center" LineBreakMode="TailTruncation" Margin="20,0,20,0"/> <Label Text="MULTILINE" FontSize="12" FontAttributes="Bold" /> <controls:MultiLineLabel Text="Welcome to Xamarin Forms! Let us try to create a label with text that is too long to fit on 1 line so it should wrap and implement an ellipsis if it's more than 2 lines in total" VerticalOptions="Center" HorizontalOptions="Center" LineBreakMode="TailTruncation" Margin="20,0,20,0"/> <Label Text="MULTILINE - 2 lines" FontSize="12" FontAttributes="Bold" /> <controls:MultiLineLabel Text="Welcome to Xamarin Forms! Let us try to create a label with text that is too long to fit on 1 line so it should wrap and implement an ellipsis if it's more than 2 lines in total" VerticalOptions="Center" HorizontalOptions="Center" LineBreakMode="TailTruncation" Lines="2" Margin="20,0,20,0"/> <Label Text="MULTILINE - 3 lines" FontSize="12" FontAttributes="Bold" /> <controls:MultiLineLabel Text="Welcome to Xamarin Forms! Let us try to create a label with text that is too long to fit on 1 line so it should wrap and implement an ellipsis if it's more than 2 lines in total" VerticalOptions="Center" HorizontalOptions="Center" LineBreakMode="TailTruncation" Lines="3" Margin="20,0,20,0"/> </StackLayout> |
We will be using this Lines property in our custom renderers.
First up the iOS custom renderer for our MultiLineLabel. Create a class in the Xamarin forms iOS project called CustomMultiLineLabelRenderer.cs with following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[assembly: ExportRenderer(typeof(MultiLineLabel), typeof(CustomMultiLineLabelRenderer))] namespace LabelWrappingTest.iOS { public class CustomMultiLineLabelRenderer : LabelRenderer { protected override void OnElementChanged(ElementChangedEventArgs<Label> e) { base.OnElementChanged(e); MultiLineLabel multiLineLabel = (MultiLineLabel)Element; if (multiLineLabel != null && multiLineLabel.Lines != -1) Control.Lines = multiLineLabel.Lines; } } } |
You’ll notice that we are checking if the user specified a value for the Lines dependency property and if this is true, we pass this to the actual iOS UIKit.UILabel control by setting it’s Lines property. With this in place we get following result.
So iOS is done and looking great. Now add the Android renderer. Create a CustomMultiLineLabelRenderer.cs class in the Xamarin forms Android project with following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class CustomMultiLineLabelRenderer : LabelRenderer { protected override void OnElementChanged(ElementChangedEventArgs<Label> e) { base.OnElementChanged(e); MultiLineLabel multiLineLabel = (MultiLineLabel)Element; if (multiLineLabel != null && multiLineLabel.Lines != -1) { Control.SetLines(multiLineLabel.Lines); } } } |
This doesn’t look all that different from the iOS counterpart… instead of setting a property we now use a method SetLines on the Android.Widget.TextView indicating how many lines we want it to display.
But wait, if we try this and look at the result, we’ll notice that it doesn’t work!!
It took me a while to figure this out ( I’m no Android expert 😉 ). But after taking a look in the Xamarin forms source code ( glad it’s open source 😉 ) I noticed they will always force a SetSingleLine(true) when setting the TailTruncation LineBreakMode…
Code can be seen here https://github.com/xamarin/Xamarin.Forms/blob/2d9288eee6e6f197364a64308183725e7bd561f9/Xamarin.Forms.Platform.Android/Renderers/LabelRenderer.cs#L179
So the fix is easy… you need to reset this Single Line forcing, the final code looks like this ( in your custom renderer ) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[assembly: ExportRenderer(typeof(MultiLineLabel), typeof(CustomMultiLineLabelRenderer))] namespace LabelWrappingTest.Droid { public class CustomMultiLineLabelRenderer : LabelRenderer { protected override void OnElementChanged(ElementChangedEventArgs<Label> e) { base.OnElementChanged(e); MultiLineLabel multiLineLabel = (MultiLineLabel)Element; if (multiLineLabel != null && multiLineLabel.Lines != -1) { Control.SetSingleLine(false); Control.SetLines(multiLineLabel.Lines); } } } } |
With that in place we’ll get following result
Yeah success!
To start I’ve done a pull request on Xamarin forms to counter this SingleLine forcing, but not sure this will be added though… https://github.com/xamarin/Xamarin.Forms/pull/234
So better be safe and add the extra line in your own custom renderer for now.
As always example project up on github here…