Skip to content

Commit

Permalink
Remove MeasureInvalidated propagation on non-legacy layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
albyrock87 committed Nov 4, 2024
1 parent 8a11f59 commit 5bef429
Show file tree
Hide file tree
Showing 21 changed files with 148 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ void IMauiPlatformView.InvalidateAncestorsMeasuresWhenMovedToWindow()
_invalidateParentWhenMovedToWindow = true;
}

void IMauiPlatformView.InvalidateMeasure() => SetNeedsLayout();

public override void MovedToWindow()
{
base.MovedToWindow();
Expand Down
34 changes: 20 additions & 14 deletions src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,30 @@ public override void ViewWillAppear(bool animated)
public override void ViewWillLayoutSubviews()
{
ConstrainItemsToBounds();
InvalidateLayoutIfItemsMeasureChanged();
base.ViewWillLayoutSubviews();
InvalidateMeasureIfContentSizeChanged();
LayoutEmptyView();
}

void InvalidateLayoutIfItemsMeasureChanged()
{
var visibleCells = CollectionView.VisibleCells;

var changed = false;
for (int n = 0; n < visibleCells.Length; n++)
{
if (visibleCells[n] is TemplatedCell { MeasureInvalidated: true } cell && cell.VerifyAndUpdateSize())
{
changed = true;
}
}

if (changed)
{
ItemsViewLayout.InvalidateLayout();
}
}


void MauiCollectionView.ICustomMauiCollectionViewDelegate.MovedToWindow(UIView view)
Expand Down Expand Up @@ -402,27 +421,14 @@ protected object GetItemAtIndex(NSIndexPath index)
return ItemsSource[index];
}

[UnconditionalSuppressMessage("Memory", "MEM0003", Justification = "Proven safe in test: CollectionViewTests.ItemsSourceDoesNotLeak")]
void CellContentSizeChanged(object sender, EventArgs e)
{
if (_disposed)
return;

if (!(sender is TemplatedCell cell))
{
return;
}

var visibleCells = CollectionView.VisibleCells;

for (int n = 0; n < visibleCells.Length; n++)
{
if (cell == visibleCells[n])
{
ItemsViewLayout?.InvalidateLayout();
return;
}
}
CollectionView.SetNeedsLayout();
}

[UnconditionalSuppressMessage("Memory", "MEM0003", Justification = "Proven safe in test: CollectionViewTests.ItemsSourceDoesNotLeak")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ void IMauiPlatformView.InvalidateAncestorsMeasuresWhenMovedToWindow()
_invalidateParentWhenMovedToWindow = true;
}

void IMauiPlatformView.InvalidateMeasure()
{
// No-op during propagation
}

[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = IUIViewLifeCycleEvents.UnconditionalSuppressMessage)]
EventHandler? _movedToWindow;

Expand Down
46 changes: 33 additions & 13 deletions src/Controls/src/Core/Handlers/Items/iOS/TemplatedCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Microsoft.Maui.Controls.Handlers.Items
{
public abstract class TemplatedCell : ItemsViewCell
public abstract class TemplatedCell : ItemsViewCell, IMauiPlatformView
{
readonly WeakEventManager _weakEventManager = new();

Expand Down Expand Up @@ -40,6 +40,7 @@ public DataTemplate CurrentTemplate
// Keep track of the cell size so we can verify whether a measure invalidation
// actually changed the size of the cell
Size _size;
bool _bound;

internal CGSize CurrentSize => _size.ToCGSize();

Expand All @@ -50,6 +51,9 @@ protected TemplatedCell(CGRect frame) : base(frame)
}

WeakReference<IPlatformViewHandler> _handler;
bool _measureInvalidated;

internal bool MeasureInvalidated => _measureInvalidated;

internal IPlatformViewHandler PlatformHandler
{
Expand Down Expand Up @@ -77,9 +81,10 @@ protected void ClearConstraints()

internal void Unbind()
{
_bound = false;

if (PlatformHandler?.VirtualView is View view)
{
view.MeasureInvalidated -= MeasureInvalidated;
view.BindingContext = null;
}
}
Expand Down Expand Up @@ -120,6 +125,7 @@ CGSize UpdateCellSize()
var nativeBounds = platformView.Frame.ToRectangle();
PlatformHandler.VirtualView.Arrange(nativeBounds);
_size = nativeBounds.Size;
_measureInvalidated = false;

return size;
}
Expand Down Expand Up @@ -160,7 +166,6 @@ public void Bind(DataTemplate template, object bindingContext, ItemsView itemsVi
// Remove the old view, if it exists
if (oldElement != null)
{
oldElement.MeasureInvalidated -= MeasureInvalidated;
oldElement.BindingContext = null;
itemsView.RemoveLogicalChild(oldElement);
ClearSubviews();
Expand All @@ -172,6 +177,7 @@ public void Bind(DataTemplate template, object bindingContext, ItemsView itemsVi

// Set the binding context _before_ we create the renderer; that way, it's available during OnElementChanged
view.BindingContext = bindingContext;
_bound = true;

var renderer = TemplateHelpers.GetHandler(view, itemsView.FindMauiContext());
SetRenderer(renderer);
Expand All @@ -190,13 +196,12 @@ public void Bind(DataTemplate template, object bindingContext, ItemsView itemsVi
if (oldElement != null)
{
oldElement.BindingContext = bindingContext;
oldElement.MeasureInvalidated += MeasureInvalidated;

UpdateCellSize();
_bound = true;
}
}

CurrentTemplate = itemTemplate;
((IMauiPlatformView)this).InvalidateMeasure();
}

void SetRenderer(IPlatformViewHandler renderer)
Expand All @@ -211,8 +216,6 @@ void SetRenderer(IPlatformViewHandler renderer)
InitializeContentConstraints(platformView);

UpdateVisualStates();

(renderer.VirtualView as View).MeasureInvalidated += MeasureInvalidated;
}

void ClearSubviews()
Expand All @@ -230,7 +233,9 @@ internal void UseContent(TemplatedCell measurementCell)
ConstrainedSize = measurementCell.ConstrainedSize;
CurrentTemplate = measurementCell.CurrentTemplate;
_size = measurementCell._size;
_bound = true;
SetRenderer(measurementCell.PlatformHandler);
((IMauiPlatformView)this).InvalidateMeasure();
}

bool IsUsingVSMForSelectionColor(View view)
Expand Down Expand Up @@ -280,20 +285,30 @@ public override bool Selected

protected abstract (bool, Size) NeedsContentSizeUpdate(Size currentSize);

void MeasureInvalidated(object sender, EventArgs args)
void IMauiPlatformView.InvalidateMeasure()
{
// If the cell is not bound (or getting unbounded), we don't want to measure it
// and cause a useless and harming InvalidateLayout on the collection view layout
if (!_measureInvalidated && _bound)
{
_measureInvalidated = true;
OnContentSizeChanged();
}
}

internal bool VerifyAndUpdateSize()
{
_measureInvalidated = false;
var (needsUpdate, toSize) = NeedsContentSizeUpdate(_size);

if (!needsUpdate)
{
return;
return false;
}

// Cache the size for next time
_size = toSize;

// Let the controller know that things need to be arranged again
OnContentSizeChanged();
return true;
}

protected void OnContentSizeChanged()
Expand Down Expand Up @@ -344,5 +359,10 @@ void UpdateSelectionColor(View view)
SelectedBackgroundView.BackgroundColor = UIColor.Clear;
}
}

void IMauiPlatformView.InvalidateAncestorsMeasuresWhenMovedToWindow()
{
// This is a no-op for cells
}
}
}
10 changes: 10 additions & 0 deletions src/Controls/src/Core/ILegacyChildMeasureInvalidated.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using Microsoft.Maui.Controls.Internals;

namespace Microsoft.Maui.Controls;

[Obsolete("This is part of the legacy layout system and will be removed in a future release")]
internal interface ILegacyChildMeasureInvalidated
{
void OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger);
}
84 changes: 22 additions & 62 deletions src/Controls/src/Core/LegacyLayouts/Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Size ILayoutManager.ArrangeChildren(Rect bounds)
/// Base class for layouts that allow you to arrange and group UI controls in your application.
/// </summary>
[Obsolete("Use Microsoft.Maui.Controls.Layout instead. For more information, see https://learn.microsoft.com/dotnet/maui/user-interface/layouts/custom")]
public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement, IView, IVisualTreeElement, IInputTransparentContainerElement
public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement, IView, IVisualTreeElement, IInputTransparentContainerElement, ILegacyChildMeasureInvalidated
{
/// <summary>Bindable property for <see cref="IsClippedToBounds"/>.</summary>
public static readonly BindableProperty IsClippedToBoundsProperty =
Expand Down Expand Up @@ -196,16 +196,16 @@ private protected override IList<Element> LogicalChildrenInternalBackingStore
#pragma warning disable CS0672 // Member overrides obsolete member
#pragma warning disable CS0618 // Type or member is obsolete
public override SizeRequest Measure(double widthConstraint, double heightConstraint, MeasureFlags flags = MeasureFlags.None)
#pragma warning restore CS0618 // Type or member is obsolete
{
#pragma warning disable CS0618 // Type or member is obsolete
SizeRequest size = base.Measure(widthConstraint - Padding.HorizontalThickness, heightConstraint - Padding.VerticalThickness, flags);
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
return new SizeRequest(new Size(size.Request.Width + Padding.HorizontalThickness, size.Request.Height + Padding.VerticalThickness),
new Size(size.Minimum.Width + Padding.HorizontalThickness, size.Minimum.Height + Padding.VerticalThickness));
#pragma warning restore CS0618 // Type or member is obsolete
var request = new Size(size.Request.Width + Padding.HorizontalThickness, size.Request.Height + Padding.VerticalThickness);
var minimum = new Size(size.Minimum.Width + Padding.HorizontalThickness, size.Minimum.Height + Padding.VerticalThickness);

DesiredSize = request;

return new SizeRequest(request, minimum);
}
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning restore CS0672 // Member overrides obsolete member

/// <summary>
Expand Down Expand Up @@ -320,14 +320,19 @@ public void RaiseChild(View view)
[Obsolete("Use InvalidateMeasure depending on your scenario")]
protected virtual void InvalidateLayout()
{
_hasDoneLayout = false;
SetNeedsLayout();
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
if (!_hasDoneLayout)
{
ForceLayout();
}
}

private void SetNeedsLayout()
{
_hasDoneLayout = false;
}

/// <summary>
/// Positions and sizes the children of a layout.
/// </summary>
Expand All @@ -341,10 +346,16 @@ protected virtual void InvalidateLayout()
[Obsolete("Use ArrangeOverride")]
protected abstract void LayoutChildren(double x, double y, double width, double height);

internal override void OnChildMeasureInvalidatedInternal(VisualElement child, InvalidationTrigger trigger)
[Obsolete("This method will be removed in the next major release.")]
void ILegacyChildMeasureInvalidated.OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger)
{
// TODO: once we remove old Xamarin public signatures we can invoke `OnChildMeasureInvalidated(VisualElement, InvalidationTrigger)` directly
SetNeedsLayout();
InvalidateMeasureCache();

OnChildMeasureInvalidated(child, new InvalidationEventArgs(trigger));

var propagatedTrigger = trigger == InvalidationTrigger.RendererReady ? trigger : InvalidationTrigger.MeasureChanged;
(Parent as ILegacyChildMeasureInvalidated)?.OnChildMeasureInvalidated(this, propagatedTrigger);
}

/// <summary>
Expand All @@ -356,8 +367,6 @@ internal override void OnChildMeasureInvalidatedInternal(VisualElement child, In
/// <remarks>This method has a default implementation and application developers must call the base implementation.</remarks>
protected void OnChildMeasureInvalidated(object sender, EventArgs e)
{
InvalidationTrigger trigger = (e as InvalidationEventArgs)?.Trigger ?? InvalidationTrigger.Undefined;
OnChildMeasureInvalidated((VisualElement)sender, trigger);
OnChildMeasureInvalidated();
}

Expand Down Expand Up @@ -531,42 +540,6 @@ internal static void LayoutChildIntoBoundingRegion(View child, Rect region, Size
child.Layout(region);
}

internal virtual void OnChildMeasureInvalidated(VisualElement child, InvalidationTrigger trigger)
{
IReadOnlyList<Element> children = LogicalChildrenInternal;
int count = children.Count;
for (var index = 0; index < count; index++)
{
if (LogicalChildrenInternal[index] is VisualElement v && v.IsVisible && (!v.IsPlatformEnabled || !v.IsPlatformStateConsistent))
{
return;
}
}

if (child is View view)
{
// we can ignore the request if we are either fully constrained or when the size request changes and we were already fully constrained
if ((trigger == InvalidationTrigger.MeasureChanged && view.Constraint == LayoutConstraint.Fixed) ||
(trigger == InvalidationTrigger.SizeRequestChanged && view.ComputedConstraint == LayoutConstraint.Fixed))
{
return;
}
if (trigger == InvalidationTrigger.HorizontalOptionsChanged || trigger == InvalidationTrigger.VerticalOptionsChanged)
{
ComputeConstraintForView(view);
}
}

if (trigger == InvalidationTrigger.RendererReady)
{
InvalidateMeasureInternal(InvalidationTrigger.RendererReady);
}
else
{
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
}
}

internal override void OnIsVisibleChanged(bool oldValue, bool newValue)
{
base.OnIsVisibleChanged(oldValue, newValue);
Expand Down Expand Up @@ -677,19 +650,6 @@ bool ShouldLayoutChildren()
return true;
}

protected override void InvalidateMeasureOverride()
{
base.InvalidateMeasureOverride();

foreach (var child in ((IElementController)this).LogicalChildren)
{
if (child is IView fe)
{
fe.InvalidateMeasure();
}
}
}

protected override Size ArrangeOverride(Rect bounds)
{
base.ArrangeOverride(bounds);
Expand Down
Loading

0 comments on commit 5bef429

Please sign in to comment.