3

I am manually positioning labels in an AbsoluteLayout. To do this correctly I would like to know the label height prior to placing it on the UI.

I have found this solution, but not without actually placing a label:

public double MeasureLabelHeight(string text, double width, double fontSize, double lineHeight, string fontFamily)
    {
        Label label = new Label();
        label.WidthRequest = width;
        label.FontSize = fontSize;
        label.LineHeight = lineHeight;
        label.FontFamily = fontFamily;
        label.LineBreakMode = LineBreakMode.WordWrap;
        label.Text = text;
        MyAbsoluteLayout.Children.Add(view: label, position: new Point(0, Height)); //place out of sight
        var sizeRequest = label.Measure(widthConstraint: width, heightConstraint: double.MaxValue, flags: MeasureFlags.None);
        var labelTextHeight = sizeRequest.Request.Height;
        MyAbsoluteLayout.Children.Remove(label);
        return labelTextHeight;
    }

This solution works on UWP, I still have to test it on Android and iOS.

I would like to improve on it though. I am unable to get a correct Height value without actually placing it in the AbsoluteLayout (out of view) and am a bit worried about the overhead this probably causes with extra redraws.

I have found an old piece of code that seemingly uses native code to do this without actually placing it in the UI for iOS and Android. I'm wondering if there is a solution available that has no need for platform specific code.

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196
Sjors Miltenburg
  • 2,540
  • 4
  • 33
  • 60

1 Answers1

0

Let Xamarin Forms measure them for you. Then you move them into position.

Do this by subclassing AbsoluteLayout, and adding an Action that a page can set, to be called when your layout has done LayoutChildren.

MyAbsoluteLayout.cs:

using System;
using Xamarin.Forms;

namespace XFSOAnswers
{
    public class MyAbsoluteLayout : AbsoluteLayout
    {
        public MyAbsoluteLayout()
        {
        }

        // Containing page will set this, to act on children during LayoutChildren.
        public Action CustomLayoutAction { get; set; }

        private bool _busy;

        protected override void LayoutChildren(double x, double y, double width, double height)
        {
            // Avoid recursed layout calls as CustomLayoutAction moves children.
            if (_busy)
                return;

            // Xamarin measures the children.
            base.LayoutChildren(x, y, width, height);

            _busy = true;
            try
            {
                CustomLayoutAction?.Invoke();
            }
            finally
            {
                _busy = false;
                // Layout again, to position the children, based on adjusted (x,y)s.
                base.LayoutChildren(x, y, width, height);
            }
        }
    }
}

Example usage - MyAbsoluteLayoutPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XFSOAnswers"
             x:Class="XFSOAnswers.MyAbsoluteLayoutPage">
    <ContentPage.Content>
        <local:MyAbsoluteLayout x:Name="TheLayout">
            <!-- Layout positions start (0,0). Adjusted later in PositionLabels. -->
            <Label x:Name="Label1" Text="Welcome" />
            <Label x:Name="Label2" Text="to" />
            <Label x:Name="Label3" Text="Xamarin" />
            <Label x:Name="Label4" Text=".Forms!" />
        </local:MyAbsoluteLayout>
    </ContentPage.Content>
</ContentPage>

MyAbsoluteLayoutPage.xaml.cs:

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace XFSOAnswers
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MyAbsoluteLayoutPage : ContentPage
    {
        public MyAbsoluteLayoutPage()
        {
            InitializeComponent();

            TheLayout.CustomLayoutAction = PositionLabels;
        }

        private void PositionLabels()
        {
            // Optional: Set breakpoint after these, to check that the bounds have values.
            var bounds1 = Label1.Bounds;
            var bounds2 = Label2.Bounds;
            var bounds3 = Label3.Bounds;
            var bounds4 = Label4.Bounds;

            double x = 10;
            double y = 20;
            MoveAbsoluteChildTo(Label1, x, y);
            x += Label1.Width;
            y += Label1.Height;

            MoveAbsoluteChildTo(Label2, x, y);
            x += Label2.Width;
            y += Label2.Height;

            MoveAbsoluteChildTo(Label3, x, y);
            x += Label3.Width;
            y += Label3.Height;

            MoveAbsoluteChildTo(Label4, x, y);
        }

        private static void MoveAbsoluteChildTo(View child, double x, double y)
        {
            AbsoluteLayout.SetLayoutBounds(child, new Rect(x, y, child.Width, child.Height));
        }
    }
}

Result:

Screenshot of AbsoluteLayout text


See MyAbsoluteLayout and MyAbsoluteLayoutPage in ToolmakerSteve - repo XFormsSOAnswers.

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196