Ajustar Label multilínea en Xamarin Forms

¡Hola!
Hoy vamos a ver cómo podemos ajustar el ancho del texto de una Label que contiene más de una línea en Xamarin Forms. El problema ocurre cuando tenemos una Label y el texto es lo suficientemente largo para que se muestre en más de una línea, en ese momento (en cuanto el texto ocupa más de una línea), la Label siempre se expandirá horizontalmente dentro de su contenedor. Dicho así, seguramente sonará un poco enrevesado, pero vamos a verlo con extracto de XAML y una imagen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    <Grid
        Padding="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
 
        <Label
            TextColor="Black"
            HorizontalOptions="Start"         
            Grid.Column="0"
            Text="SIT VOLUPT ACCUSANTIU"/>
 
        <Label
            Grid.Column="1"
            Text="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam"/>
 
    </Grid>

Con lo que hemos dicho, junto con la imagen, pues quizá no esté del todo claro.. vamos a ponerle fondo rojo al texto de la izquierda.

Ahora queda bastante más claro. Como podemos apreciar el texto de la izquierda se expande horizontalmente hasta el final de su columna aunque hemos hecho un vago intento de que no lo haga indicando HorizontalOptions=»Start». Llegados a este punto dirás, ¿y qué más me da que se expanda? No parece que nos vaya a afectar visualmente a ningún diseño. Pero…

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
    <Grid
        Padding="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
 
         <Frame
            CornerRadius="10"
            VerticalOptions="Start"
            HorizontalOptions="Start"
            HasShadow="false"
            Padding="8"
            BackgroundColor="Black">
 
            <Label
                TextColor="White"   
                Text="SIT VOLUPT ACCUSANTIU"/>
 
        </Frame>
 
        <Label
            Grid.Column="1"
            Text="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam"/>
 
    </Grid>

Ahora sí que parece que tenemos un problema. Intentamos rodear el texto con un Frame con los bordes redondeados, algo más o menos habitual cuando se añaden categorías a diseños. Si el texto fuese en una única línea no nos afectaría, ya que en ese caso sí se ajusta correctamente al ancho que tenga el texto, pero como hemos visto, en cuanto tiene más de una línea siempre se va a expandir horizontalmente hasta el final del contenedor.
Para solucionar esto vamos a hacerlo a través de un renderer. En primer lugar vamos a crear en nuestro código compartido un nuevo tipo de label llamado TightLabel.

1
2
3
4
    public class TightLabel : Label
    {
 
    }

Si ya tenemos una Label y sus renderers asociados, podríamos aprovechar y añadirle una propiedad para añadir este comportamiento (por ejemplo un booleano IsTightLabel), pero como no es nuestro caso, hemos creado una clase nueva. Ahora toca el turno de los renderers. Empezamos con la implementación de iOS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public class TightLabelRenderer : LabelRenderer
    {
          public override void LayoutSubviews()
          {
            base.LayoutSubviews();
 
            if (Control?.Text != null && Element != null)
            {
                NSString nsStr = new NSString(Control.Text);
                  var frame = nsStr.GetBoundingRect(new CGSize(this.NativeView.Frame.Size.Width, double.MaxValue),
                                                               NSStringDrawingOptions.UsesLineFragmentOrigin | NSStringDrawingOptions.UsesFontLeading,
                                                               new UIStringAttributes() { Font = Control.Font }, null);
 
                  Element.Layout(new Rectangle(Element.Bounds.X, Element.Bounds.Y, frame.Width, frame.Height));
                  if (Element.Parent is Layout parent)
                  {
                      //Esta seguramente no sea la mejor solución... El problema es que el contenedor de esta TightLabelRenderer, no se redimensiona automáticamente
                      parent.Layout(new Rectangle(parent.Bounds.X, parent.Bounds.Y, frame.Width + parent.Padding.HorizontalThickness, frame.Height + parent.Padding.VerticalThickness));
                  }
              }
          }
    }

Y ahora toca el turno a la implementación de Android:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    public class TightLabelRenderer : LabelRenderer
    {
        public TightLabelRenderer(Context context) : base(context)
        {
        }
 
        protected override TextView CreateNativeControl()
        {
            return new TightFormTextView(Context);
        }
    }
 
    public class TightFormTextView : FormsTextView
    {
        public TightFormTextView(Context context) : base(context)
        {
        }
 
        public TightFormTextView(Context context, IAttributeSet attrs) : base(context, attrs)
        {
        }
 
        public TightFormTextView(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
        {
        }
 
        protected TightFormTextView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
        {
        }
 
        protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
        {
            base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
            var specModeW = MeasureSpec.GetMode(widthMeasureSpec);
            if (specModeW != Android.Views.MeasureSpecMode.Exactly)
            {
                Layout layout = Layout;
                int linesCount = layout.LineCount;
                if (linesCount > 1)
                {
                    float textRealMaxWidth = 0;
                    for (int n = 0; n < linesCount; ++n)
                    {
                        textRealMaxWidth = Math.Max(textRealMaxWidth, layout.GetLineWidth(n));
                    }
                    int w = (int)Math.Round(textRealMaxWidth);
                    if (w < MeasuredWidth)
                    {
                        base.OnMeasure(MeasureSpec.MakeMeasureSpec(w, Android.Views.MeasureSpecMode.Exactly),
                                heightMeasureSpec);
                    }
                }
            }
        }
    }

Cambiamos en nuestro XAML la Label por nuestra nueva TightLabel:

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
    <Grid
        Padding="10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
 
         <Frame
            CornerRadius="10"
            VerticalOptions="Start"
            HorizontalOptions="Start"
            HasShadow="false"
            Padding="8"
            BackgroundColor="Black">
 
            <local:TightLabel
                TextColor="White"   
                Text="SIT VOLUPT ACCUSANTIU"/>
 
        </Frame>
 
        <Label
            Grid.Column="1"
            Text="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam"/>
 
    </Grid>

Compilamos y…
Ahora ya se ajusta bien nuestra Label y el contenedor al ancho real del texto. Es posible que haya alguna otra forma de conseguirlo, pero puede sacarte de un apuro 😉
¡Un saludo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *