2
votes

[UPDATE]: I modified my custom renderer to handle this situation. See below!

While developing a Xamrin Forms (Android + iOS) app, I'm experiencing a strange behavour with labels: if I place a label with empty text, it is rendered fine on Android, while on iOS it is not rendered at all.

Here is a simple example:

XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
     xmlns:d="http://xamarin.com/schemas/2014/forms/design"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     mc:Ignorable="d"
     x:Class="MyApp.Views.Page1"
     xmlns:ctrl="clr-namespace:MyApp.Controls">
<ContentPage.Content>
    <StackLayout Spacing="0">
        <Label x:Name="LabelOs" Margin="0,20" />

        <Label x:Name="Label1" Text="Label1" BackgroundColor="Red" TextColor="White" Margin="0" Padding="0" />
        <Label x:Name="Label2" Text="" BackgroundColor="Green" TextColor="White" Margin="0" Padding="0" />
        <Label x:Name="Label3" Text="Label3" BackgroundColor="Blue" TextColor="White" Margin="0" Padding="0" />

        <ctrl:RRLabel x:Name="Label1Custom" Text="Label1Custom" BackgroundColor="Yellow" Borders="Left, Right, Bottom" Margin="10,50,10,10" />
        <ctrl:RRLabel x:Name="Label2Custom" Text="" Borders="Left, Right, Bottom" BackgroundColor="Yellow" Margin="10" />
        <ctrl:RRLabel x:Name="Label3Custom" Text="Label3Custom" Borders="Left, Right, Bottom" BackgroundColor="Yellow" Margin="10" />

        <Button x:Name="Button1" Text="Set Label2" Clicked="Button1_Clicked" Margin="20, 50, 20, 0" />

    </StackLayout>
</ContentPage.Content>

Code behind:

private void Button1_Clicked(object sender, EventArgs e)
{
    Label2.Text = "Label2";
    Label2Custom.Text = "Label2Custom";
}

Label custom renderer (relevant part only):

public override void Draw(CGRect rect)
{

    using (CGContext g = UIGraphics.GetCurrentContext())
    {
        base.Draw(rect);

        g.SetLineWidth(2);
        g.SetStrokeColor(lineColor);

        var path = new CGPath();

        if ((label.Borders & RRLabel.LabelBorders.Left) == RRLabel.LabelBorders.Left)
            path.AddLines(new CGPoint[] { new CGPoint(rect.Left, rect.Top), new CGPoint(rect.Left, rect.Bottom) });

        if ((label.Borders & RRLabel.LabelBorders.Bottom) == RRLabel.LabelBorders.Bottom)
            path.AddLines(new CGPoint[] { new CGPoint(rect.Left, rect.Bottom), new CGPoint(rect.Right, rect.Bottom) });

        if ((label.Borders & RRLabel.LabelBorders.Right) == RRLabel.LabelBorders.Right)
            path.AddLines(new CGPoint[] { new CGPoint(rect.Right, rect.Bottom), new CGPoint(rect.Right, rect.Top) });

        if ((label.Borders & RRLabel.LabelBorders.Top) == RRLabel.LabelBorders.Top)
            path.AddLines(new CGPoint[] { new CGPoint(rect.Right, rect.Top), new CGPoint(rect.Left, rect.Top) });

        g.AddPath(path);
        g.DrawPath(CGPathDrawingMode.Stroke);
    }
}

This is how the app looks like on Android:

enter image description here

And on iOS:

enter image description here

As you can see, both standard and custom labels have issues: they both have height=0, so their background is not displayed. When text is set, standard label il rendered correctly, while in the custom one there is no background and borders.

Of course everything is fine if I set label text to a space, but I would like to avoid this.

[SOLUTION]: I managed to handle this by modifying my custom renderer:

Shared control:

public class RRLabel : Label
{
    /* ... */

    public bool IosForceHeight { get; set; } = true;
}

Renderer:

protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
    base.OnElementChanged(e);

    if (Control != null && e.NewElement != null)
    {
        /* ... */
        if (label.IosForceHeight && label.Text == "") Control.Text = " ";
    }
}

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

    /* ... */
    if (label.IosForceHeight && e.PropertyName == nameof(RRLabel.Text) && label.Text == "") Control.Text = " ";
}

This way I have the label always rendered, while preserving the actual value of the Text property in the shared control.

2

2 Answers

1
votes

That's the default behavior of iOS platform.

I would give a solution that set the label's text to " " to make the label appear on iOS side.

For example:

<Label x:Name="Label2" Text=" " BackgroundColor="Green" TextColor="White" Margin="0"  />
1
votes

Yes. That's one of the differences of rendering between the 2 platforms.

It's constistent with the native implementation. On iOS, a Label with a text has an height of 0. On Android, it takes the height required for any Label.

My solution to not render a Label with an empty text is to use a converter on the IsVisible property.

In the opposite way, if you want to take an height anyway, set the HeightRequest of the Label.

Hope it helps !