Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[net7.0] Flag valid measure status in iOS backing views to avoid repeat measuring #12933

Merged
merged 1 commit into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
using Microsoft.Maui.Handlers;
using System.Threading.Tasks;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Handlers;
using Xunit;
using Microsoft.Maui.Platform;
using ContentView = Microsoft.Maui.Controls.ContentView;

namespace Microsoft.Maui.DeviceTests
{
Expand All @@ -13,5 +19,137 @@ static int GetContentChildCount(ContentViewHandler contentViewHandler)
{
return contentViewHandler.PlatformView.Subviews[0].Subviews.Length;
}

[Fact, Category(TestCategory.Layout)]
public async Task ContentViewHasExpectedSize()
{
var contentView = new ContentView();
var label = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };
contentView.Content = label;

var size = await InvokeOnMainThreadAsync(() =>
{
var labelHandler = CreateHandler<LabelHandler>(label);
var handler = CreateHandler<ContentViewHandler>(contentView);

var targetSize = handler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
handler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return handler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, size.Width);
Assert.Equal(100, size.Height);
}

[Fact, Category(TestCategory.Layout)]
public async Task ContentViewRespondsWhenViewAdded()
{
var contentView = new ContentView();
var label = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };

var size = await InvokeOnMainThreadAsync(() =>
{
var labelHandler = CreateHandler<LabelHandler>(label);
var handler = CreateHandler<ContentViewHandler>(contentView);

var targetSize = handler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
handler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return handler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(0, size.Width);
Assert.Equal(0, size.Height);

var updatedSize = await InvokeOnMainThreadAsync(() =>
{
var handler = CreateHandler<ContentViewHandler>(contentView);

contentView.Content = label;

var targetSize = handler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
handler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return handler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, updatedSize.Width);
Assert.Equal(100, updatedSize.Height);
}

[Fact, Category(TestCategory.Layout)]
public async Task ContentViewRespondsWhenViewRemoved()
{
var contentView = new ContentView();
var label = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };
contentView.Content = label;

var size = await InvokeOnMainThreadAsync(() =>
{
var labelHandler = CreateHandler<LabelHandler>(label);
var handler = CreateHandler<ContentViewHandler>(contentView);

var targetSize = handler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
handler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return handler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, size.Width);
Assert.Equal(100, size.Height);

var updatedSize = await InvokeOnMainThreadAsync(() =>
{
var handler = CreateHandler<ContentViewHandler>(contentView);

contentView.Content = null;

var targetSize = handler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
handler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return handler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(0, updatedSize.Width);
Assert.Equal(0, updatedSize.Height);
}

[Fact, Category(TestCategory.Layout)]
public async Task ContentViewRespondsWhenViewUpdated()
{
var contentView = new ContentView();
var label = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };
contentView.Content = label;

var size = await InvokeOnMainThreadAsync(() =>
{
var labelHandler = CreateHandler<LabelHandler>(label);
var handler = CreateHandler<ContentViewHandler>(contentView);

var targetSize = handler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
handler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return handler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, size.Width);
Assert.Equal(100, size.Height);

var updatedSize = await InvokeOnMainThreadAsync(() =>
{
var handler = CreateHandler<ContentViewHandler>(contentView);

label.HeightRequest = 300;

var targetSize = handler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
handler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return handler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, updatedSize.Width);
Assert.Equal(300, updatedSize.Height);
}
}
}
154 changes: 154 additions & 0 deletions src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Xunit;

namespace Microsoft.Maui.DeviceTests
{
public partial class LayoutTests
{
[Fact, Category(TestCategory.Layout)]
public async Task LayoutViewHasExpectedSize()
{
var layout = new VerticalStackLayout();
var label = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };
layout.Add(label);

var size = await InvokeOnMainThreadAsync(() =>
{
var labelHandler = CreateHandler<LabelHandler>(label);
var layoutHandler = CreateHandler<LayoutHandler>(layout);

var targetSize = layoutHandler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
layoutHandler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return layoutHandler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, size.Width);
Assert.Equal(100, size.Height);
}

[Fact, Category(TestCategory.Layout)]
public async Task LayoutViewRespondsWhenViewAdded()
{
var layout = new VerticalStackLayout();
var label = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };
var secondLabel = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };
layout.Add(label);

var size = await InvokeOnMainThreadAsync(() =>
{
var labelHandler = CreateHandler<LabelHandler>(label);
var layoutHandler = CreateHandler<LayoutHandler>(layout);

var targetSize = layoutHandler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
layoutHandler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return layoutHandler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, size.Width);
Assert.Equal(100, size.Height);

var updatedSize = await InvokeOnMainThreadAsync(() =>
{
var labelHandler2 = CreateHandler<LabelHandler>(secondLabel);
var layoutHandler = CreateHandler<LayoutHandler>(layout);

layout.Add(secondLabel);

var targetSize = layoutHandler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
layoutHandler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return layoutHandler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, updatedSize.Width);
Assert.Equal(200, updatedSize.Height);
}

[Fact, Category(TestCategory.Layout)]
public async Task LayoutViewRespondsWhenViewRemoved()
{
var layout = new VerticalStackLayout();
var label = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };
var secondLabel = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };
layout.Add(label);
layout.Add(secondLabel);

var size = await InvokeOnMainThreadAsync(() =>
{
var labelHandler = CreateHandler<LabelHandler>(label);
var labelHandler2 = CreateHandler<LabelHandler>(secondLabel);
var layoutHandler = CreateHandler<LayoutHandler>(layout);

var targetSize = layoutHandler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
layoutHandler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return layoutHandler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, size.Width);
Assert.Equal(200, size.Height);

var updatedSize = await InvokeOnMainThreadAsync(() =>
{
var layoutHandler = CreateHandler<LayoutHandler>(layout);

layout.Remove(secondLabel);

var targetSize = layoutHandler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
layoutHandler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return layoutHandler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, updatedSize.Width);
Assert.Equal(100, updatedSize.Height);
}

[Fact, Category(TestCategory.Layout)]
public async Task LayoutViewRespondsWhenViewUpdated()
{
var layout = new VerticalStackLayout();
var label = new Label { Text = "", HeightRequest = 100, WidthRequest = 100 };
layout.Add(label);

var size = await InvokeOnMainThreadAsync(() =>
{
var labelHandler = CreateHandler<LabelHandler>(label);
var layoutHandler = CreateHandler<LayoutHandler>(layout);

var targetSize = layoutHandler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
layoutHandler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return layoutHandler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, size.Width);
Assert.Equal(100, size.Height);

var updatedSize = await InvokeOnMainThreadAsync(() =>
{
var layoutHandler = CreateHandler<LayoutHandler>(layout);

label.HeightRequest = 300;

var targetSize = layoutHandler.VirtualView.Measure(double.PositiveInfinity, double.PositiveInfinity);
layoutHandler.VirtualView.Arrange(new Rect(Point.Zero, targetSize));

return layoutHandler.PlatformView.Bounds.Size.ToSize();
});

Assert.Equal(100, updatedSize.Width);
Assert.Equal(300, updatedSize.Height);
}
}
}
10 changes: 9 additions & 1 deletion src/Core/src/Platform/iOS/ContentView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class ContentView : MauiView
WeakReference<IBorderStroke>? _clip;
CAShapeLayer? _childMaskLayer;
internal event EventHandler? LayoutSubviewsChanged;
bool _measureValid;

public override CGSize SizeThatFits(CGSize size)
{
Expand All @@ -23,6 +24,7 @@ public override CGSize SizeThatFits(CGSize size)
var height = size.Height;

var crossPlatformSize = CrossPlatformMeasure(width, height);
_measureValid = true;

return crossPlatformSize.ToCGSize();
}
Expand All @@ -33,7 +35,12 @@ public override void LayoutSubviews()

var bounds = AdjustForSafeArea(Bounds).ToRectangle();

CrossPlatformMeasure?.Invoke(bounds.Width, bounds.Height);
if (!_measureValid)
{
CrossPlatformMeasure?.Invoke(bounds.Width, bounds.Height);
_measureValid = true;
}

CrossPlatformArrange?.Invoke(bounds);

if (ChildMaskLayer != null)
Expand All @@ -46,6 +53,7 @@ public override void LayoutSubviews()

public override void SetNeedsLayout()
{
_measureValid = false;
base.SetNeedsLayout();
Superview?.SetNeedsLayout();
}
Expand Down
13 changes: 12 additions & 1 deletion src/Core/src/Platform/iOS/LayoutView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.Maui.Platform
public class LayoutView : MauiView
{
bool _userInteractionEnabled;
bool _measureValid;

// TODO: Possibly reconcile this code with ViewHandlerExtensions.MeasureVirtualView
// If you make changes here please review if those changes should also
Expand All @@ -23,6 +24,7 @@ public override CGSize SizeThatFits(CGSize size)
var height = size.Height;

var crossPlatformSize = CrossPlatformMeasure(width, height);
_measureValid = true;

return crossPlatformSize.ToCGSize();
}
Expand All @@ -35,24 +37,33 @@ public override void LayoutSubviews()
base.LayoutSubviews();

var bounds = AdjustForSafeArea(Bounds).ToRectangle();
CrossPlatformMeasure?.Invoke(bounds.Width, bounds.Height);

if (!_measureValid)
{
CrossPlatformMeasure?.Invoke(bounds.Width, bounds.Height);
_measureValid = true;
}

CrossPlatformArrange?.Invoke(bounds);
}

public override void SetNeedsLayout()
{
_measureValid = false;
base.SetNeedsLayout();
Superview?.SetNeedsLayout();
}

public override void SubviewAdded(UIView uiview)
{
_measureValid = false;
base.SubviewAdded(uiview);
Superview?.SetNeedsLayout();
}

public override void WillRemoveSubview(UIView uiview)
{
_measureValid = false;
base.WillRemoveSubview(uiview);
Superview?.SetNeedsLayout();
}
Expand Down