diff --git a/src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.iOS.cs index 0e38b37d5927..b97f0b9688fb 100644 --- a/src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.iOS.cs @@ -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 { @@ -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(label); + var handler = CreateHandler(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(label); + var handler = CreateHandler(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(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(label); + var handler = CreateHandler(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(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(label); + var handler = CreateHandler(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(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); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs new file mode 100644 index 000000000000..471a8fd32a52 --- /dev/null +++ b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs @@ -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(label); + var layoutHandler = CreateHandler(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(label); + var layoutHandler = CreateHandler(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(secondLabel); + var layoutHandler = CreateHandler(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(label); + var labelHandler2 = CreateHandler(secondLabel); + var layoutHandler = CreateHandler(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(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(label); + var layoutHandler = CreateHandler(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(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); + } + } +} diff --git a/src/Core/src/Platform/iOS/ContentView.cs b/src/Core/src/Platform/iOS/ContentView.cs index 9a4d87d433c8..2b275adfeb4d 100644 --- a/src/Core/src/Platform/iOS/ContentView.cs +++ b/src/Core/src/Platform/iOS/ContentView.cs @@ -11,6 +11,7 @@ public class ContentView : MauiView WeakReference? _clip; CAShapeLayer? _childMaskLayer; internal event EventHandler? LayoutSubviewsChanged; + bool _measureValid; public override CGSize SizeThatFits(CGSize size) { @@ -23,6 +24,7 @@ public override CGSize SizeThatFits(CGSize size) var height = size.Height; var crossPlatformSize = CrossPlatformMeasure(width, height); + _measureValid = true; return crossPlatformSize.ToCGSize(); } @@ -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) @@ -46,6 +53,7 @@ public override void LayoutSubviews() public override void SetNeedsLayout() { + _measureValid = false; base.SetNeedsLayout(); Superview?.SetNeedsLayout(); } diff --git a/src/Core/src/Platform/iOS/LayoutView.cs b/src/Core/src/Platform/iOS/LayoutView.cs index aaccf7fa1a10..7620ae6eeebb 100644 --- a/src/Core/src/Platform/iOS/LayoutView.cs +++ b/src/Core/src/Platform/iOS/LayoutView.cs @@ -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 @@ -23,6 +24,7 @@ public override CGSize SizeThatFits(CGSize size) var height = size.Height; var crossPlatformSize = CrossPlatformMeasure(width, height); + _measureValid = true; return crossPlatformSize.ToCGSize(); } @@ -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(); }