From e95f4984a7cb2c8c322b71acaa937e2faaa3df2b Mon Sep 17 00:00:00 2001 From: Alberto Aldegheri Date: Fri, 20 Dec 2024 17:55:12 +0100 Subject: [PATCH 01/12] Simplify iOS ScrollView --- .../Handlers/Items2/ItemsViewHandler2.iOS.cs | 13 +- .../Elements/ContentView/ContentViewTests.cs | 15 +- .../ScrollView/ScrollViewTests.iOS.cs | 8 +- .../TestCases.HostApp/Issues/Issue26629.cs | 61 ++++ .../Tests/Issues/Issue26629.cs | 29 ++ .../ScrollView/ScrollViewHandler.iOS.cs | 274 ++---------------- src/Core/src/Platform/iOS/MauiScrollView.cs | 126 +++++++- .../PublicAPI/net-ios/PublicAPI.Unshipped.txt | 8 +- .../net-maccatalyst/PublicAPI.Unshipped.txt | 6 + 9 files changed, 275 insertions(+), 265 deletions(-) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue26629.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26629.cs diff --git a/src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs b/src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs index 2a9709fb6045..02fd3a27bb61 100644 --- a/src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs +++ b/src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Text; +using CoreGraphics; using Foundation; using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; @@ -67,7 +68,17 @@ private protected override UIView OnCreatePlatformView() protected abstract ItemsViewController2 CreateController(TItemsView newElement, UICollectionViewLayout layout); - protected override UIView CreatePlatformView() => Controller?.View; + protected override UIView CreatePlatformView() + { + UIView controllerView = Controller?.View ?? throw new InvalidOperationException("ItemsViewController2's view should not be null at this point."); + + // Reset the bounds and center, as they are set to the size of the screen by default + // but we want SizeThatFits to return the actual desired size. + controllerView.Bounds = new CGRect(); + controllerView.Center = new CGPoint(); + + return controllerView; + } public static void MapItemsSource(ItemsViewHandler2 handler, ItemsView itemsView) { diff --git a/src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.cs b/src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.cs index 258939f5580b..6497f1dbd283 100644 --- a/src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.cs @@ -28,17 +28,16 @@ void SetupBuilder() public async Task ControlTemplateUpdates() { SetupBuilder(); - var child = new Label { Text = "Content 1" }; - var contentView = new Microsoft.Maui.Controls.ContentView(); - var header = new Label { Text = "Header" }; - var footer = new Label { Text = "Footer" }; - var presenter = new ContentPresenter(); - var grid = new Grid(); - - var contentViewHandler = await CreateHandlerAsync(contentView); await InvokeOnMainThreadAsync(() => { + var child = new Label { Text = "Content 1" }; + var contentView = new Microsoft.Maui.Controls.ContentView(); + var header = new Label { Text = "Header" }; + var footer = new Label { Text = "Footer" }; + var presenter = new ContentPresenter(); + var grid = new Grid(); + var contentViewHandler = CreateHandler(contentView); contentView.Content = child; Assert.True(GetChildCount(contentViewHandler) == 1); Assert.True(GetContentChildCount(contentViewHandler) == 0); diff --git a/src/Controls/tests/DeviceTests/Elements/ScrollView/ScrollViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/ScrollView/ScrollViewTests.iOS.cs index 23cf3433070e..1be8e4d32bb5 100644 --- a/src/Controls/tests/DeviceTests/Elements/ScrollView/ScrollViewTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/ScrollView/ScrollViewTests.iOS.cs @@ -46,12 +46,6 @@ public async Task ContentSizeExpandsToViewport() var entry = new Entry() { Text = "In a ScrollView", HeightRequest = 10 }; - - static CoreGraphics.CGSize getViewportSize(UIScrollView scrollView) - { - return scrollView.AdjustedContentInset.InsetRect(scrollView.Bounds).Size; - }; - var scrollViewHandler = await InvokeOnMainThreadAsync(() => { return CreateHandlerAsync(scrollView); @@ -76,7 +70,7 @@ await scrollViewHandler.PlatformView.AttachAndRun(async () => await Task.Yield(); var contentSize = uiScrollView.ContentSize; - var viewportSize = getViewportSize(uiScrollView); + var viewportSize = uiScrollView.Bounds.Size; Assert.Equal(viewportSize.Height, contentSize.Height); Assert.Equal(viewportSize.Width, contentSize.Width); diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue26629.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue26629.cs new file mode 100644 index 000000000000..01c31710b410 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue26629.cs @@ -0,0 +1,61 @@ +namespace Maui.Controls.Sample.Issues +{ + [Issue(IssueTracker.Github, 26629, "ScrollView resizes when content changes", PlatformAffected.All)] + public class Issue26629 : ContentPage + { + public Issue26629() + { + var grid = new Grid + { + Margin = 40, + RowSpacing = 16, + BackgroundColor = Colors.Beige, + RowDefinitions = new RowDefinitionCollection( + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Auto), + new RowDefinition(GridLength.Star)), + ColumnDefinitions = new ColumnDefinitionCollection(new ColumnDefinition(GridLength.Star)), + }; + var scrollView = new ScrollView(); + var scrollViewVsl = new VerticalStackLayout(); + var button = new Button + { + Text = "Add Label", + AutomationId = "AddLabelButton", + }; + var sizeLabel = new Label + { + AutomationId = "SizeLabel", + }; + sizeLabel.SetBinding(Label.TextProperty, new Binding("Height", source: scrollView)); + + var i = 0; + scrollView.BackgroundColor = Colors.LightBlue; + scrollView.Padding = 16; + scrollView.VerticalOptions = LayoutOptions.Start; + scrollViewVsl.Children.Add(CreateLabel("Label0")); + button.Clicked += (sender, args) => + { + scrollViewVsl.Children.Add(CreateLabel($"Label{++i}")); + }; + + scrollView.Content = scrollViewVsl; + grid.Add(button, 0, 0); + grid.Add(sizeLabel, 0, 1); + grid.Add(scrollView, 0, 2); + + Content = grid; + } + + static Label CreateLabel(string automationId) + { + return new Label + { + Text = "Huge Label", + FontSize = 50, + BackgroundColor = Colors.SlateBlue, + AutomationId = automationId, + }; + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26629.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26629.cs new file mode 100644 index 000000000000..c653aa2f0c85 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26629.cs @@ -0,0 +1,29 @@ +using NUnit.Framework; +using NUnit.Framework.Legacy; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues +{ + public class Issue26629 : _IssuesUITest + { + public override string Issue => "ScrollView resizes when content changes"; + + public Issue26629(TestDevice device) + : base(device) + { } + + [Test] + [Category(UITestCategories.ScrollView)] + public void ScrollViewResizesWhenContentChanges() + { + var sizeLabel = App.WaitForElement("SizeLabel"); + var initialSize = double.Parse(sizeLabel.GetText()!); + var addLabelButton = App.WaitForElement("AddLabelButton"); + addLabelButton.Tap(); + App.WaitForElement("Label1"); + var newSize = double.Parse(sizeLabel.GetText()!); + ClassicAssert.Greater(newSize, initialSize); + } + } +} diff --git a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs index b2b657e2964c..1028ffe07704 100644 --- a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs +++ b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs @@ -3,13 +3,12 @@ using Microsoft.Maui.Graphics; using Microsoft.Maui.Layouts; using UIKit; -using Size = Microsoft.Maui.Graphics.Size; namespace Microsoft.Maui.Handlers { - public partial class ScrollViewHandler : ViewHandler, ICrossPlatformLayout + public partial class ScrollViewHandler : ViewHandler { - const nint ContentPanelTag = 0x845fed; + const nint ContentTag = 0x845fed; readonly ScrollEventProxy _eventProxy = new(); @@ -34,6 +33,16 @@ protected override UIScrollView CreatePlatformView() return new MauiScrollView(); } + public override void SetVirtualView(IView view) + { + base.SetVirtualView(view); + + if (PlatformView is MauiScrollView platformScrollView && view is IScrollView scrollView) + { + platformScrollView.View = scrollView; + } + } + protected override void ConnectHandler(UIScrollView platformView) { base.ConnectHandler(platformView); @@ -54,14 +63,9 @@ public static void MapContent(IScrollViewHandler handler, IScrollView scrollView if (handler.PlatformView == null || handler.MauiContext == null) return; - if (handler is not ICrossPlatformLayout crossPlatformLayout) - { - return; - } - // We'll use the local cross-platform layout methods defined in our handler (which wrap the ScrollView's default methods) // so we can normalize the behavior of the scrollview to match the other platforms - UpdateContentView(scrollView, handler, crossPlatformLayout); + UpdateContentView(scrollView, handler); } // We don't actually have this mapped because we don't need it, but we can't remove it because it's public @@ -87,22 +91,12 @@ public static void MapVerticalScrollBarVisibility(IScrollViewHandler handler, IS public static void MapOrientation(IScrollViewHandler handler, IScrollView scrollView) { - if (handler?.PlatformView is not UIScrollView uiScrollView) + if (handler?.PlatformView is not { } platformView) { return; } - // If the UIScrollView hasn't been arranged yet, this will basically do nothing. - // If it has been, we can just update the ContentSize here and get the new orientation working - // without having to re-layout the ScrollView - - var fullContentSize = scrollView.PresentedContent?.DesiredSize ?? Size.Zero; - - var viewportSize = GetViewportSize(uiScrollView); - var viewportWidth = viewportSize.Width; - var viewportHeight = viewportSize.Height; - - SetContentSizeForOrientation(uiScrollView, viewportWidth, viewportHeight, scrollView.Orientation, fullContentSize); + platformView.InvalidateMeasure(scrollView); } public static void MapRequestScrollTo(IScrollViewHandler handler, IScrollView scrollView, object? args) @@ -123,7 +117,7 @@ public static void MapRequestScrollTo(IScrollViewHandler handler, IScrollView sc var availableScrollWidth = uiScrollView.ContentSize.Width - uiScrollView.Frame.Width; var minScrollHorizontal = Math.Min(request.HorizontalOffset, availableScrollWidth); var minScrollVertical = Math.Min(request.VerticalOffset, availableScrollHeight); - uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(minScrollHorizontal, minScrollVertical), !request.Instant); + uiScrollView.SetContentOffset(new CGPoint(minScrollHorizontal, minScrollVertical), !request.Instant); if (request.Instant) { @@ -132,236 +126,35 @@ public static void MapRequestScrollTo(IScrollViewHandler handler, IScrollView sc } } - // Find the internal ContentView; it may not be Subviews[0] because of the scrollbars - static ContentView? GetContentView(UIScrollView scrollView) + static UIView? GetContentView(UIScrollView scrollView) { - for (int n = 0; n < scrollView.Subviews.Length; n++) + for (int i = 0; i < scrollView.Subviews.Length; i++) { - if (scrollView.Subviews[n] is ContentView contentView) + if (scrollView.Subviews[i] is { Tag: ContentTag } contentView) { - if (contentView.Tag is nint tag && tag == ContentPanelTag) - { - return contentView; - } + return contentView; } } return null; } - static void UpdateContentView(IScrollView scrollView, IScrollViewHandler handler, ICrossPlatformLayout crossPlatformLayout) - { - if (scrollView.PresentedContent == null || handler.MauiContext == null) - { - return; - } - - var platformScrollView = handler.PlatformView; - var nativeContent = scrollView.PresentedContent.ToPlatform(handler.MauiContext); - - if (GetContentView(platformScrollView) is ContentView currentContentContainer) - { - if (currentContentContainer.Subviews.Length == 0 || currentContentContainer.Subviews[0] != nativeContent) - { - currentContentContainer.ClearSubviews(); - currentContentContainer.AddSubview(nativeContent); - currentContentContainer.View = scrollView.PresentedContent; - } - } - else - { - InsertContentView(platformScrollView, scrollView, nativeContent, crossPlatformLayout); - } - } - - static void InsertContentView(UIScrollView platformScrollView, IScrollView scrollView, UIView platformContent, ICrossPlatformLayout crossPlatformLayout) - { - if (scrollView.PresentedContent == null) - { - return; - } - - var contentContainer = new ContentView() - { - View = scrollView.PresentedContent, - Tag = ContentPanelTag - }; - - // This is where we normally would inject the cross-platform ScrollView's layout logic; instead, we're injecting the - // methods from this handler so it can make some adjustments for things like Padding before the default logic is invoked - contentContainer.CrossPlatformLayout = crossPlatformLayout; - - platformScrollView.ClearSubviews(); - contentContainer.AddSubview(platformContent); - platformScrollView.AddSubview(contentContainer); - } - - public override Size GetDesiredSize(double widthConstraint, double heightConstraint) - { - var virtualView = VirtualView; - var crossPlatformLayout = virtualView as ICrossPlatformLayout; - var platformView = PlatformView; - - if (platformView == null || virtualView == null) - { - return new Size(widthConstraint, heightConstraint); - } - - var padding = virtualView.Padding; - - // Account for the ScrollView Padding before measuring the content - widthConstraint = AccountForPadding(widthConstraint, padding.HorizontalThickness); - heightConstraint = AccountForPadding(heightConstraint, padding.VerticalThickness); - - var crossPlatformContentSize = crossPlatformLayout.CrossPlatformMeasure(widthConstraint, heightConstraint); - - // Add the padding back in for the final size - crossPlatformContentSize.Width += padding.HorizontalThickness; - crossPlatformContentSize.Height += padding.VerticalThickness; - - var viewportWidth = Math.Min(crossPlatformContentSize.Width, widthConstraint); - var viewportHeight = Math.Min(crossPlatformContentSize.Height, heightConstraint); - - // Since the UIScrollView might not be arranged yet, we can't rely on its Bounds for the viewport height/width - // So we'll use the constraints instead. - SetContentSizeForOrientation(platformView, widthConstraint, heightConstraint, virtualView.Orientation, crossPlatformContentSize); - - var finalWidth = ViewHandlerExtensions.ResolveConstraints(viewportWidth, virtualView.Width, virtualView.MinimumWidth, virtualView.MaximumWidth); - var finalHeight = ViewHandlerExtensions.ResolveConstraints(viewportHeight, virtualView.Height, virtualView.MinimumHeight, virtualView.MaximumHeight); - - return new Size(finalWidth, finalHeight); - } - - public override void PlatformArrange(Rect rect) - { - base.PlatformArrange(rect); - - // Ensure that the content container for the ScrollView gets arranged, and is large enough - // to contain the ScrollView's content - - var contentView = GetContentView(PlatformView); - - if (contentView == null) - { - return; - } - - var desiredSize = VirtualView.PresentedContent?.DesiredSize ?? Size.Zero; - var scrollViewPadding = VirtualView.Padding; - var platformViewBounds = PlatformView.Bounds; - - var contentBounds = new CGRect(0, 0, - Math.Max(desiredSize.Width + scrollViewPadding.HorizontalThickness, platformViewBounds.Width), - Math.Max(desiredSize.Height + scrollViewPadding.VerticalThickness, platformViewBounds.Height)); - - contentView.Bounds = contentBounds; - contentView.Center = new CGPoint(contentBounds.GetMidX(), contentBounds.GetMidY()); - - if (PendingScrollToRequest != null) - { - VirtualView.RequestScrollTo(PendingScrollToRequest.HorizontalOffset, PendingScrollToRequest.VerticalOffset, PendingScrollToRequest.Instant); - PendingScrollToRequest = null; - } - } - - static double AccountForPadding(double constraint, double padding) - { - // Remove the padding from the constraint, but don't allow it to go negative - return Math.Max(0, constraint - padding); - } - - static void SetContentSizeForOrientation(UIScrollView uiScrollView, double viewportWidth, double viewportHeight, ScrollOrientation orientation, Size contentSize) - { - if (orientation is ScrollOrientation.Vertical or ScrollOrientation.Neither) - { - contentSize.Width = Math.Min(contentSize.Width, viewportWidth); - } - - if (orientation is ScrollOrientation.Horizontal or ScrollOrientation.Neither) - { - contentSize.Height = Math.Min(contentSize.Height, viewportHeight); - } - - uiScrollView.ContentSize = contentSize; - } - - static CGSize GetViewportSize(UIScrollView platformScrollView) - { - return platformScrollView.AdjustedContentInset.InsetRect(platformScrollView.Bounds).Size; - } - - Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double heightConstraint) + static void UpdateContentView(IScrollView scrollView, IScrollViewHandler handler) { - var scrollView = VirtualView; - var crossPlatformLayout = scrollView as ICrossPlatformLayout; - var platformScrollView = PlatformView; + var platformView = handler.PlatformView ?? throw new InvalidOperationException($"{nameof(PlatformView)} should have been set by base class."); + var mauiContext = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class."); - var presentedContent = scrollView.PresentedContent; - if (presentedContent == null) + if (GetContentView(platformView) is { } currentContentPlatformView) { - return Size.Zero; + currentContentPlatformView.RemoveFromSuperview(); } - var viewportSize = GetViewportSize(platformScrollView); - - var padding = scrollView.Padding; - - if (widthConstraint == 0) + if (scrollView.PresentedContent is { } content) { - widthConstraint = viewportSize.Width; + var platformContent = content.ToPlatform(mauiContext); + platformContent.Tag = ContentTag; + platformView.AddSubview(platformContent); } - - if (heightConstraint == 0) - { - heightConstraint = viewportSize.Height; - } - - // Account for the ScrollView Padding before measuring the content - widthConstraint = AccountForPadding(widthConstraint, padding.HorizontalThickness); - heightConstraint = AccountForPadding(heightConstraint, padding.VerticalThickness); - - // Now handle the actual cross-platform measurement of the ScrollView's content - var result = crossPlatformLayout.CrossPlatformMeasure(widthConstraint, heightConstraint); - - return result.AdjustForFill(new Rect(0, 0, widthConstraint, heightConstraint), presentedContent); - } - - Size ICrossPlatformLayout.CrossPlatformArrange(Rect bounds) - { - var scrollView = VirtualView; - var crossPlatformLayout = scrollView as ICrossPlatformLayout; - var platformScrollView = PlatformView; - - // The UIScrollView's bounds are available, so we can use them to make sure the ContentSize makes sense - // for the ScrollView orientation - var viewportSize = GetViewportSize(platformScrollView); - - // Get a Rect for doing the CrossPlatformArrange of the Content - var viewportRect = new Rect(Graphics.Point.Zero, viewportSize.ToSize()); - - var contentSize = crossPlatformLayout.CrossPlatformArrange(viewportRect); - - var viewportHeight = viewportSize.Height; - var viewportWidth = viewportSize.Width; - SetContentSizeForOrientation(platformScrollView, viewportWidth, viewportHeight, scrollView.Orientation, contentSize); - - var container = GetContentView(platformScrollView); - - if (container?.Superview is UIScrollView uiScrollView) - { - // Ensure the container is at least the size of the UIScrollView itself, so that the - // cross-platform layout logic makes sense and the contents don't arrange outside the - // container. (Everything will look correct if they do, but hit testing won't work properly.) - var containerBounds = contentSize; - - container.Bounds = new CGRect(0, 0, - Math.Max(containerBounds.Width, viewportSize.Width), - Math.Max(containerBounds.Height, viewportSize.Height)); - - container.Center = new CGPoint(container.Bounds.GetMidX(), container.Bounds.GetMidY()); - } - - return contentSize; } class ScrollEventProxy @@ -386,15 +179,6 @@ public void Disconnect(UIScrollView platformView) platformView.ScrollAnimationEnded -= ScrollAnimationEnded; } - void OnButtonTouchUpInside(object? sender, EventArgs e) - { - if (VirtualView is IButton virtualView) - { - virtualView.Released(); - virtualView.Clicked(); - } - } - void ScrollAnimationEnded(object? sender, EventArgs e) { VirtualView?.ScrollFinished(); diff --git a/src/Core/src/Platform/iOS/MauiScrollView.cs b/src/Core/src/Platform/iOS/MauiScrollView.cs index 00c1ca3d55e8..db34a4550802 100644 --- a/src/Core/src/Platform/iOS/MauiScrollView.cs +++ b/src/Core/src/Platform/iOS/MauiScrollView.cs @@ -1,14 +1,113 @@ using System; using System.Diagnostics.CodeAnalysis; using CoreGraphics; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Layouts; using UIKit; namespace Microsoft.Maui.Platform { public class MauiScrollView : UIScrollView, IUIViewLifeCycleEvents { - public MauiScrollView() + bool _arranged; + bool _invalidateParentWhenMovedToWindow; + double _lastMeasureHeight; + double _lastMeasureWidth; + + WeakReference? _reference; + + internal IScrollView? View + { + get => _reference != null && _reference.TryGetTarget(out var v) ? v : null; + set => _reference = value == null ? null : new(value); + } + + public override void LayoutSubviews() + { + // LayoutSubviews is invoked while scrolling, so we need to arrange the content only when it's necessary. + // This could be done via `override ScrollViewHandler.PlatformArrange` but that wouldn't cover the case + // when the ScrollView is attached to a non-MauiView parent (i.e. DeviceTests). + if (!_arranged && View is { } scrollView) + { + var bounds = Bounds; + var widthConstraint = (double)bounds.Width; + var heightConstraint = (double)bounds.Height; + + // If the SuperView is a MauiView (backing a cross-platform ContentView or Layout), then measurement + // has already happened via SizeThatFits and doesn't need to be repeated in LayoutSubviews. But we + // _do_ need LayoutSubviews to make a measurement pass if the parent is something else (for example, + // the window); there's no guarantee that SizeThatFits has been called in that case. + if (!IsMeasureValid(widthConstraint, heightConstraint) && Superview is not MauiView) + { + CrossPlatformMeasure(scrollView, widthConstraint, heightConstraint); + CacheMeasureConstraints(widthConstraint, heightConstraint); + } + + var size = scrollView.ArrangeContentUnbounded(new Rect(new Point(), bounds.Size.ToSize())); + ContentSize = size.ToCGSize(); + } + + base.LayoutSubviews(); + } + + public override CGSize SizeThatFits(CGSize size) + { + if (View is not { } scrollView) + { + return new CGSize(); + } + + var widthConstraint = (double)size.Width; + var heightConstraint = (double)size.Height; + + var measuredSize = CrossPlatformMeasure(scrollView, widthConstraint, heightConstraint); + CacheMeasureConstraints(widthConstraint, heightConstraint); + + return measuredSize; + } + + public override void SetNeedsLayout() + { + base.SetNeedsLayout(); + InvalidateConstraintsCache(); + _arranged = false; + + TryToInvalidateSuperView(false); + } + + static Size CrossPlatformMeasure(IScrollView scrollView, double widthConstraint, double heightConstraint) + { + if (scrollView.Orientation is ScrollOrientation.Horizontal or ScrollOrientation.Both) + { + widthConstraint = double.PositiveInfinity; + } + + if (scrollView.Orientation is ScrollOrientation.Vertical or ScrollOrientation.Both) + { + heightConstraint = double.PositiveInfinity; + } + + var measuredSize = scrollView.MeasureContent(scrollView.Padding, widthConstraint, heightConstraint); + return measuredSize; + } + + bool IsMeasureValid(double widthConstraint, double heightConstraint) + { + // Check the last constraints this View was measured with; if they're the same, + // then the current measure info is already correct, and we don't need to repeat it + return heightConstraint == _lastMeasureHeight && widthConstraint == _lastMeasureWidth; + } + + void InvalidateConstraintsCache() { + _lastMeasureWidth = double.NaN; + _lastMeasureHeight = double.NaN; + } + + void CacheMeasureConstraints(double widthConstraint, double heightConstraint) + { + _lastMeasureWidth = widthConstraint; + _lastMeasureHeight = heightConstraint; } // overriding this method so it does not automatically scroll large UITextFields @@ -19,8 +118,29 @@ public override void ScrollRectToVisible(CGRect rect, bool animated) base.ScrollRectToVisible(rect, animated); } + private protected void TryToInvalidateSuperView(bool shouldOnlyInvalidateIfPending) + { + if (shouldOnlyInvalidateIfPending && !_invalidateParentWhenMovedToWindow) + { + return; + } + + // We check for Window to avoid scenarios where an invalidate might propagate up the tree + // To a SuperView that's been disposed which will cause a crash when trying to access it + if (Window is not null) + { + this.Superview?.SetNeedsLayout(); + _invalidateParentWhenMovedToWindow = false; + } + else + { + _invalidateParentWhenMovedToWindow = true; + } + } + [UnconditionalSuppressMessage("Memory", "MEM0002", Justification = IUIViewLifeCycleEvents.UnconditionalSuppressMessage)] EventHandler? _movedToWindow; + event EventHandler IUIViewLifeCycleEvents.MovedToWindow { add => _movedToWindow += value; @@ -31,7 +151,7 @@ public override void MovedToWindow() { base.MovedToWindow(); _movedToWindow?.Invoke(this, EventArgs.Empty); + TryToInvalidateSuperView(true); } } -} - +} \ No newline at end of file diff --git a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt index d7c0c90a8422..1ac058c6bf46 100644 --- a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -53,6 +53,12 @@ Microsoft.Maui.WebProcessTerminatedEventArgs.Sender.get -> WebKit.WKWebView! override Microsoft.Maui.Handlers.HybridWebViewHandler.ConnectHandler(WebKit.WKWebView! platformView) -> void override Microsoft.Maui.Handlers.HybridWebViewHandler.CreatePlatformView() -> WebKit.WKWebView! override Microsoft.Maui.Handlers.HybridWebViewHandler.DisconnectHandler(WebKit.WKWebView! platformView) -> void +override Microsoft.Maui.Handlers.ScrollViewHandler.SetVirtualView(Microsoft.Maui.IView! view) -> void +override Microsoft.Maui.Platform.MauiScrollView.LayoutSubviews() -> void +override Microsoft.Maui.Platform.MauiScrollView.SetNeedsLayout() -> void +override Microsoft.Maui.Platform.MauiScrollView.SizeThatFits(CoreGraphics.CGSize size) -> CoreGraphics.CGSize +*REMOVED*override Microsoft.Maui.Handlers.ScrollViewHandler.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size +*REMOVED*override Microsoft.Maui.Handlers.ScrollViewHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void static Microsoft.Maui.ElementHandlerExtensions.GetRequiredService(this Microsoft.Maui.IElementHandler! handler, System.Type! type) -> T static Microsoft.Maui.ElementHandlerExtensions.GetRequiredService(this Microsoft.Maui.IElementHandler! handler) -> T static Microsoft.Maui.ElementHandlerExtensions.GetService(this Microsoft.Maui.IElementHandler! handler, System.Type! type) -> T? @@ -80,4 +86,4 @@ override Microsoft.Maui.Platform.MauiCALayer.Dispose(bool disposing) -> void override Microsoft.Maui.Platform.MauiCALayer.RemoveFromSuperLayer() -> void override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void override Microsoft.Maui.Handlers.EditorHandler.NeedsContainer.get -> bool -override Microsoft.Maui.Handlers.WindowHandler.DisconnectHandler(UIKit.UIWindow! platformView) -> void +override Microsoft.Maui.Handlers.WindowHandler.DisconnectHandler(UIKit.UIWindow! platformView) -> void \ No newline at end of file diff --git a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index d7c0c90a8422..bb8880b916bf 100644 --- a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -53,6 +53,12 @@ Microsoft.Maui.WebProcessTerminatedEventArgs.Sender.get -> WebKit.WKWebView! override Microsoft.Maui.Handlers.HybridWebViewHandler.ConnectHandler(WebKit.WKWebView! platformView) -> void override Microsoft.Maui.Handlers.HybridWebViewHandler.CreatePlatformView() -> WebKit.WKWebView! override Microsoft.Maui.Handlers.HybridWebViewHandler.DisconnectHandler(WebKit.WKWebView! platformView) -> void +override Microsoft.Maui.Handlers.ScrollViewHandler.SetVirtualView(Microsoft.Maui.IView! view) -> void +override Microsoft.Maui.Platform.MauiScrollView.LayoutSubviews() -> void +override Microsoft.Maui.Platform.MauiScrollView.SetNeedsLayout() -> void +override Microsoft.Maui.Platform.MauiScrollView.SizeThatFits(CoreGraphics.CGSize size) -> CoreGraphics.CGSize +*REMOVED*override Microsoft.Maui.Handlers.ScrollViewHandler.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size +*REMOVED*override Microsoft.Maui.Handlers.ScrollViewHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void static Microsoft.Maui.ElementHandlerExtensions.GetRequiredService(this Microsoft.Maui.IElementHandler! handler, System.Type! type) -> T static Microsoft.Maui.ElementHandlerExtensions.GetRequiredService(this Microsoft.Maui.IElementHandler! handler) -> T static Microsoft.Maui.ElementHandlerExtensions.GetService(this Microsoft.Maui.IElementHandler! handler, System.Type! type) -> T? From a92ae433a2efb8b1f5cc80101bd50c2af61367d5 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Fri, 20 Dec 2024 11:26:40 -0600 Subject: [PATCH 02/12] - fix test to more accurately match our templates --- .../TestCases.HostApp/Issues/Issues17801.xaml | 64 ++++++++++--------- .../Issues/Issues17801.xaml.cs | 2 +- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issues17801.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issues17801.xaml index b7adb7b484e9..23f53a23f33e 100644 --- a/src/Controls/tests/TestCases.HostApp/Issues/Issues17801.xaml +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issues17801.xaml @@ -1,31 +1,35 @@  - - - - -