Skip to content

Commit 391b33d

Browse files
authored
Avoid useless measure passes on iOS (#31485)
* Avoid useless measure passes on iOS * Refactor condition for simplicty
1 parent 28a1e47 commit 391b33d

File tree

5 files changed

+56
-15
lines changed

5 files changed

+56
-15
lines changed

src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ await InvokeOnMainThreadAsync(async () =>
7474

7575
// Wait for image to load and force the grid to measure itself again
7676
await Task.Delay(1000);
77-
#pragma warning disable CS0618 // Type or member is obsolete
77+
78+
// The layout and buttons are not connected to a window, so the measure invalidation won't propagate.
79+
// Therefore, we have to invalidate and measure the layout manually.
80+
layout.InvalidateMeasure();
7881
layout.Measure(double.PositiveInfinity, double.PositiveInfinity);
79-
#pragma warning restore CS0618 // Type or member is obsolete
8082

8183
await handler.ToPlatform().AssertContainsColor(Colors.Blue, MauiContext); // Grid renders
8284
await handler.ToPlatform().AssertContainsColor(Colors.Red, MauiContext); // Image within button renders

src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue25671.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ public async Task LayoutPassesShouldNotIncrease()
4242
var arrangePasses = int.Parse(match.Groups[2].Value);
4343

4444
#if IOS
45-
var maxMeasurePasses = 225;
46-
var maxArrangePasses = 247;
45+
var maxMeasurePasses = 203;
46+
var maxArrangePasses = 214;
4747

4848
if (App.FindElement("HeadingLabel").GetText() == "CollectionViewHandler2")
4949
{
50-
maxMeasurePasses = 380;
51-
maxArrangePasses = 295;
50+
maxMeasurePasses = 373;
51+
maxArrangePasses = 276;
5252
}
5353
#elif ANDROID
5454
const int maxMeasurePasses = 353;

src/Core/src/Platform/iOS/MauiView.cs

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ public abstract class MauiView : UIView, ICrossPlatformLayoutBacking, IVisualTre
3535
/// </summary>
3636
double _lastMeasureWidth = double.NaN;
3737

38+
/// <summary>
39+
/// Cached measured cross-platform size from the last measure operation tight to <see cref="_lastMeasureWidth"/> and <see cref="_lastMeasureHeight"/>.
40+
/// Used to avoid redundant measure calls when constraints haven't changed.
41+
/// NaN indicates no previous measure has been performed.
42+
/// </summary>
43+
Size? _lastMeasuredSize;
44+
3845
/// <summary>
3946
/// Current safe area padding values (top, left, bottom, right) in device-independent units.
4047
/// These values represent the insets needed to avoid system UI elements like status bars,
@@ -348,15 +355,34 @@ SafeAreaPadding GetAdjustedSafeAreaInsets()
348355
/// <returns>True if the cached measure is still valid, false otherwise</returns>
349356
protected bool IsMeasureValid(double widthConstraint, double heightConstraint)
350357
{
351-
return !HasFixedConstraints
352-
&& widthConstraint == _lastMeasureWidth
358+
return widthConstraint == _lastMeasureWidth
353359
&& heightConstraint == _lastMeasureHeight;
354360
}
355361

362+
/// <summary>
363+
/// Caches the measure constraints and the resulting measured size from the last measure operation.
364+
/// </summary>
365+
/// <param name="widthConstraint">The width constraint used.</param>
366+
/// <param name="heightConstraint">The height constraint used.</param>
367+
[Obsolete("Use CacheMeasureConstraints(double widthConstraint, double heightConstraint, Size measuredSize) instead.")]
356368
protected void CacheMeasureConstraints(double widthConstraint, double heightConstraint)
357369
{
358370
_lastMeasureWidth = widthConstraint;
359371
_lastMeasureHeight = heightConstraint;
372+
_lastMeasuredSize = null;
373+
}
374+
375+
/// <summary>
376+
/// Caches the measure constraints and the resulting measured size from the last measure operation.
377+
/// </summary>
378+
/// <param name="widthConstraint">The width constraint used.</param>
379+
/// <param name="heightConstraint">The height constraint used.</param>
380+
/// <param name="measuredSize">The resulting measured cross-platform size.</param>
381+
protected void CacheMeasureConstraints(double widthConstraint, double heightConstraint, Size measuredSize)
382+
{
383+
_lastMeasureWidth = widthConstraint;
384+
_lastMeasureHeight = heightConstraint;
385+
_lastMeasuredSize = measuredSize;
360386
}
361387

362388
/// <summary>
@@ -366,7 +392,7 @@ protected void CacheMeasureConstraints(double widthConstraint, double heightCons
366392
/// <returns>True if the view has been measured, false otherwise</returns>
367393
bool HasBeenMeasured()
368394
{
369-
return !double.IsNaN(_lastMeasureWidth) && !double.IsNaN(_lastMeasureHeight);
395+
return _lastMeasuredSize.HasValue;
370396
}
371397

372398
/// <summary>
@@ -378,6 +404,7 @@ protected void InvalidateConstraintsCache()
378404
{
379405
_lastMeasureWidth = double.NaN;
380406
_lastMeasureHeight = double.NaN;
407+
_lastMeasuredSize = null;
381408
}
382409

383410
public ICrossPlatformLayout? CrossPlatformLayout
@@ -446,9 +473,14 @@ public override CGSize SizeThatFits(CGSize size)
446473
var widthConstraint = (double)size.Width;
447474
var heightConstraint = (double)size.Height;
448475

449-
var crossPlatformSize = CrossPlatformMeasure(widthConstraint, heightConstraint);
476+
if (IsMeasureValid(widthConstraint, heightConstraint) && _lastMeasuredSize is { } crossPlatformSize)
477+
{
478+
return crossPlatformSize.ToCGSize();
479+
}
480+
481+
crossPlatformSize = CrossPlatformMeasure(widthConstraint, heightConstraint);
450482

451-
CacheMeasureConstraints(widthConstraint, heightConstraint);
483+
CacheMeasureConstraints(widthConstraint, heightConstraint, crossPlatformSize);
452484

453485
return crossPlatformSize.ToCGSize();
454486
}
@@ -499,11 +531,16 @@ public override void LayoutSubviews()
499531
// imposed by the parent (i.e. scroll view) with the current bounds, except when our bounds are fixed by constraints.
500532
// But we _do_ need LayoutSubviews to make a measurement pass if the parent is something else (for example,
501533
// the window); there's no guarantee that SizeThatFits has been called in that case.
502-
if (!IsMeasureValid(widthConstraint, heightConstraint) && !this.IsFinalMeasureHandledBySuperView() ||
503-
!HasBeenMeasured() && HasFixedConstraints)
534+
var needsMeasure = HasFixedConstraints switch
535+
{
536+
true => !HasBeenMeasured() || !this.IsFinalMeasureHandledBySuperView(),
537+
false => !IsMeasureValid(widthConstraint, heightConstraint) && !this.IsFinalMeasureHandledBySuperView()
538+
};
539+
540+
if (needsMeasure)
504541
{
505-
CrossPlatformMeasure(widthConstraint, heightConstraint);
506-
CacheMeasureConstraints(widthConstraint, heightConstraint);
542+
var crossPlatformSize = CrossPlatformMeasure(widthConstraint, heightConstraint);
543+
CacheMeasureConstraints(widthConstraint, heightConstraint, crossPlatformSize);
507544
}
508545

509546
CrossPlatformArrange(bounds);

src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Microsoft.Maui.Platform.MauiTimePicker.UpdateTime(System.TimeSpan? time) -> void
5959
*REMOVED*Microsoft.Maui.Platform.MauiWebViewNavigationDelegate.DidFailNavigation(WebKit.WKWebView! webView, WebKit.WKNavigation! navigation, Foundation.NSError! error) -> void
6060
*REMOVED*Microsoft.Maui.Platform.MauiWebViewNavigationDelegate.DidFailProvisionalNavigation(WebKit.WKWebView! webView, WebKit.WKNavigation! navigation, Foundation.NSError! error) -> void
6161
*REMOVED*Microsoft.Maui.Platform.MauiWebViewNavigationDelegate.DidFinishNavigation(WebKit.WKWebView! webView, WebKit.WKNavigation! navigation) -> void
62+
Microsoft.Maui.Platform.MauiView.CacheMeasureConstraints(double widthConstraint, double heightConstraint, Microsoft.Maui.Graphics.Size measuredSize) -> void
6263
Microsoft.Maui.Platform.TextInputExtensions
6364
Microsoft.Maui.Platform.UIApplicationExtensions
6465
Microsoft.Maui.SafeAreaEdges

src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Microsoft.Maui.Platform.MauiTimePicker.UpdateTime(System.TimeSpan? time) -> void
6161
*REMOVED*Microsoft.Maui.Platform.MauiWebViewNavigationDelegate.DidFailNavigation(WebKit.WKWebView! webView, WebKit.WKNavigation! navigation, Foundation.NSError! error) -> void
6262
*REMOVED*Microsoft.Maui.Platform.MauiWebViewNavigationDelegate.DidFailProvisionalNavigation(WebKit.WKWebView! webView, WebKit.WKNavigation! navigation, Foundation.NSError! error) -> void
6363
*REMOVED*Microsoft.Maui.Platform.MauiWebViewNavigationDelegate.DidFinishNavigation(WebKit.WKWebView! webView, WebKit.WKNavigation! navigation) -> void
64+
Microsoft.Maui.Platform.MauiView.CacheMeasureConstraints(double widthConstraint, double heightConstraint, Microsoft.Maui.Graphics.Size measuredSize) -> void
6465
Microsoft.Maui.Platform.TextInputExtensions
6566
Microsoft.Maui.Platform.UIApplicationExtensions
6667
Microsoft.Maui.SafeAreaEdges

0 commit comments

Comments
 (0)