From bbb80452f669fde908947f3d7dca368ba98a9460 Mon Sep 17 00:00:00 2001 From: tj-devel709 Date: Tue, 2 Jul 2024 10:52:59 -0500 Subject: [PATCH] Use the CrossPlatformLayout similar to MauiView --- src/Controls/src/Core/Button/Button.iOS.cs | 35 +++--- .../PublicAPI/net-ios/PublicAPI.Shipped.txt | 1 - .../PublicAPI/net-ios/PublicAPI.Unshipped.txt | 1 - .../net-maccatalyst/PublicAPI.Shipped.txt | 1 - .../net-maccatalyst/PublicAPI.Unshipped.txt | 1 - .../src/Handlers/Button/ButtonHandler.iOS.cs | 9 ++ .../src/Handlers/ViewHandlerExtensions.iOS.cs | 2 +- src/Core/src/Platform/iOS/WrapperView.cs | 115 +++++++++++++++--- .../PublicAPI/net-ios/PublicAPI.Unshipped.txt | 2 + .../net-maccatalyst/PublicAPI.Unshipped.txt | 1 + 10 files changed, 123 insertions(+), 45 deletions(-) diff --git a/src/Controls/src/Core/Button/Button.iOS.cs b/src/Controls/src/Core/Button/Button.iOS.cs index d13b3c8a52c6..23c6eddf1747 100644 --- a/src/Controls/src/Core/Button/Button.iOS.cs +++ b/src/Controls/src/Core/Button/Button.iOS.cs @@ -8,10 +8,11 @@ using UIKit; using CoreGraphics; using Microsoft.Extensions.Logging; +using Microsoft.Maui.Layouts; namespace Microsoft.Maui.Controls { - public partial class Button + public partial class Button : ICrossPlatformLayout { CGSize _originalImageSize = CGSize.Empty; @@ -23,8 +24,8 @@ public partial class Button /// /// /// Returns a representing the width and height of the button. - /// Calling base.MeasureOverride will call SizeThatFits() on the UIButton's UIView but will not consider some MAUI Button elements such as the BorderWidth and image placement. Instead we calculate that manually. - protected override Size MeasureOverride(double widthConstraint, double heightConstraint) + /// This method is used to pseudo-override the SizeThatFits() on the UIButton since we cannot override the UIButton class. + Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double heightConstraint) { var button = this; @@ -48,11 +49,8 @@ protected override Size MeasureOverride(double widthConstraint, double heightCon if (padding.IsNaN) padding = ButtonHandler.DefaultPadding; - var buttonWidthConstraint = button.WidthRequest == -1 ? widthConstraint : Math.Min(button.WidthRequest, widthConstraint); - var buttonHeightConstraint = button.HeightRequest == -1 ? heightConstraint : Math.Min(button.HeightRequest, heightConstraint); - - var titleWidthConstraint = buttonWidthConstraint - padding.Left - padding.Right - borderWidth * 2; - var titleHeightConstraint = buttonHeightConstraint - padding.Top - padding.Bottom - borderWidth * 2; + var titleWidthConstraint = widthConstraint - padding.Left - padding.Right - borderWidth * 2; + var titleHeightConstraint = heightConstraint - padding.Top - padding.Bottom - borderWidth * 2; var image = platformButton.CurrentImage; @@ -65,7 +63,7 @@ protected override Size MeasureOverride(double widthConstraint, double heightCon } // Resize the image if necessary and then update the image variable - if (ResizeImageIfNecessary(platformButton, button, image, 0, padding, borderWidth, buttonWidthConstraint, buttonHeightConstraint, _originalImageSize)) + if (ResizeImageIfNecessary(platformButton, button, image, 0, padding, borderWidth, widthConstraint, heightConstraint, _originalImageSize)) { image = platformButton.CurrentImage; } @@ -134,12 +132,8 @@ protected override Size MeasureOverride(double widthConstraint, double heightCon } } - var returnSize = new Size(button.WidthRequest == -1 ? Math.Min(buttonContentWidth, buttonWidthConstraint) : button.WidthRequest, - button.HeightRequest == -1 ? Math.Min(buttonContentHeight, buttonHeightConstraint) : button.HeightRequest); - - // Add the margins to the return size - returnSize.Width += (nfloat)button.Margin.HorizontalThickness; - returnSize.Height += (nfloat)button.Margin.VerticalThickness; + var returnSize = new Size(Math.Min(buttonContentWidth, widthConstraint), + Math.Min(buttonContentHeight, heightConstraint)); // Rounding the values up to the nearest whole number to match UIView.SizeThatFits return new Size((int)Math.Ceiling(returnSize.Width), (int)Math.Ceiling(returnSize.Height)); @@ -149,16 +143,17 @@ protected override Size MeasureOverride(double widthConstraint, double heightCon /// Arrange the button and layout the image and title. /// /// - /// - protected override Size ArrangeOverride(Rect bounds) + /// Returns a representing the width and height of the button. + Size ICrossPlatformLayout.CrossPlatformArrange(Rect bounds) { - var button = this; + bounds = this.ComputeFrame(bounds); + var platformButton = Handler?.PlatformView as UIButton; // Layout the image and title of the button - LayoutButton(platformButton, button, bounds); + LayoutButton(platformButton, this, bounds); - return base.ArrangeOverride(bounds); + return new Size(bounds.Width, bounds.Height); } /// diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Shipped.txt index 82744bf66c6b..09ec7007ae6f 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Shipped.txt @@ -7447,7 +7447,6 @@ override Microsoft.Maui.Controls.Application.OnParentSet() -> void override Microsoft.Maui.Controls.Border.OnPropertyChanged(string? propertyName = null) -> void override Microsoft.Maui.Controls.BoxView.OnMeasure(double widthConstraint, double heightConstraint) -> Microsoft.Maui.SizeRequest override Microsoft.Maui.Controls.BoxView.OnPropertyChanged(string? propertyName = null) -> void -override Microsoft.Maui.Controls.Button.ArrangeOverride(Microsoft.Maui.Graphics.Rect bounds) -> Microsoft.Maui.Graphics.Size override Microsoft.Maui.Controls.Button.ChangeVisualState() -> void override Microsoft.Maui.Controls.Button.OnBindingContextChanged() -> void override Microsoft.Maui.Controls.Cell.OnBindingContextChanged() -> void diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt index a64b0b58c5b1..ec30a83003c7 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -1,5 +1,4 @@ #nullable enable -override Microsoft.Maui.Controls.Button.MeasureOverride(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size override Microsoft.Maui.Controls.Handlers.Compatibility.CellRenderer.Invoke(string! command, object? args) -> void override Microsoft.Maui.Controls.Handlers.Compatibility.CellRenderer.UpdateValue(string! property) -> void override Microsoft.Maui.Controls.Handlers.Compatibility.ShellRenderer.PreferredStatusBarUpdateAnimation.get -> UIKit.UIStatusBarAnimation diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt index e25e1460bf99..68f54ea67ca8 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt @@ -7447,7 +7447,6 @@ override Microsoft.Maui.Controls.Application.OnParentSet() -> void override Microsoft.Maui.Controls.Border.OnPropertyChanged(string? propertyName = null) -> void override Microsoft.Maui.Controls.BoxView.OnMeasure(double widthConstraint, double heightConstraint) -> Microsoft.Maui.SizeRequest override Microsoft.Maui.Controls.BoxView.OnPropertyChanged(string? propertyName = null) -> void -override Microsoft.Maui.Controls.Button.ArrangeOverride(Microsoft.Maui.Graphics.Rect bounds) -> Microsoft.Maui.Graphics.Size override Microsoft.Maui.Controls.Button.ChangeVisualState() -> void override Microsoft.Maui.Controls.Button.OnBindingContextChanged() -> void override Microsoft.Maui.Controls.Cell.OnBindingContextChanged() -> void diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 0631f369dd13..c77202070fd5 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -1,5 +1,4 @@ #nullable enable -override Microsoft.Maui.Controls.Button.MeasureOverride(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size override Microsoft.Maui.Controls.Handlers.Compatibility.CellRenderer.Invoke(string! command, object? args) -> void override Microsoft.Maui.Controls.Handlers.Compatibility.CellRenderer.UpdateValue(string! property) -> void override Microsoft.Maui.Controls.GradientBrush.IsEmpty.get -> bool diff --git a/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs b/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs index 100047eff21d..252eaf2f573a 100644 --- a/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs +++ b/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs @@ -25,6 +25,15 @@ protected override UIButton CreatePlatformView() readonly ButtonEventProxy _proxy = new ButtonEventProxy(); + protected override void SetupContainer() + { + base.SetupContainer(); + if (ContainerView is WrapperView wrapperView) + { + wrapperView.CrossPlatformLayout = VirtualView as ICrossPlatformLayout; + } + } + protected override void ConnectHandler(UIButton platformView) { _proxy.Connect(VirtualView, platformView); diff --git a/src/Core/src/Handlers/ViewHandlerExtensions.iOS.cs b/src/Core/src/Handlers/ViewHandlerExtensions.iOS.cs index 4e91cbd5242f..8b2e669db3d5 100644 --- a/src/Core/src/Handlers/ViewHandlerExtensions.iOS.cs +++ b/src/Core/src/Handlers/ViewHandlerExtensions.iOS.cs @@ -98,7 +98,7 @@ internal static Size GetDesiredSizeFromHandler(this IViewHandler viewHandler, do } else if (platformView is WrapperView wrapper) { - sizeThatFits = wrapper.SizeThatFitsWrapper(new CGSize((float)widthConstraint, (float)heightConstraint), virtualView.Width, virtualView.Height); + sizeThatFits = wrapper.SizeThatFitsWrapper(new CGSize((float)widthConstraint, (float)heightConstraint), virtualView.Width, virtualView.Height, virtualView); } else { diff --git a/src/Core/src/Platform/iOS/WrapperView.cs b/src/Core/src/Platform/iOS/WrapperView.cs index 4a287f70c081..4e4abd7e7591 100644 --- a/src/Core/src/Platform/iOS/WrapperView.cs +++ b/src/Core/src/Platform/iOS/WrapperView.cs @@ -11,6 +11,17 @@ namespace Microsoft.Maui.Platform { public partial class WrapperView : UIView, IDisposable, IUIViewLifeCycleEvents { + WeakReference? _crossPlatformLayoutReference; + + internal ICrossPlatformLayout? CrossPlatformLayout + { + get => _crossPlatformLayoutReference != null && _crossPlatformLayoutReference.TryGetTarget(out var v) ? v : null; + set => _crossPlatformLayoutReference = value == null ? null : new WeakReference(value); + } + + double _lastMeasureHeight = double.NaN; + double _lastMeasureWidth = double.NaN; + CAShapeLayer? _maskLayer; CAShapeLayer? _backgroundMaskLayer; CAShapeLayer? _shadowLayer; @@ -26,6 +37,19 @@ public WrapperView(CGRect frame) { } + internal 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; + } + + internal void CacheMeasureConstraints(double widthConstraint, double heightConstraint) + { + _lastMeasureWidth = widthConstraint; + _lastMeasureHeight = heightConstraint; + } + CAShapeLayer? MaskLayer { get => _maskLayer; @@ -103,6 +127,17 @@ public override void LayoutSubviews() SetClip(); SetShadow(); SetBorder(); + + var boundWidth = Bounds.Width; + var boundHeight = Bounds.Height; + + if (!IsMeasureValid(boundWidth, boundHeight)) + { + CrossPlatformLayout?.CrossPlatformMeasure(boundWidth, boundHeight); + CacheMeasureConstraints(boundWidth, boundHeight); + } + + CrossPlatformLayout?.CrossPlatformArrange(Bounds.ToRectangle()); } public new void Dispose() @@ -117,41 +152,81 @@ public override void LayoutSubviews() public override CGSize SizeThatFits(CGSize size) { var subviews = Subviews; - if (subviews.Length == 0) - return base.SizeThatFits(size); + CGSize returnSize; - var child = subviews[0]; - - // Calling SizeThatFits on an ImageView always returns the image's dimensions, so we need to call the extension method - // This also affects ImageButtons - if (child is UIImageView imageView) + if (subviews.Length == 0) { - return imageView.SizeThatFitsImage(size); + returnSize = base.SizeThatFits(size); } - else if (child is UIButton imageButton && imageButton.ImageView?.Image is not null && imageButton.CurrentTitle is null) + + else { - return imageButton.ImageView.SizeThatFitsImage(size); + var child = subviews[0]; + + // Calling SizeThatFits on an ImageView always returns the image's dimensions, so we need to call the extension method + // This also affects ImageButtons + if (child is UIImageView imageView) + { + returnSize = imageView.SizeThatFitsImage(size); + } + else if (child is UIButton imageButton && imageButton.ImageView?.Image is not null && imageButton.CurrentTitle is null) + { + returnSize = imageButton.ImageView.SizeThatFitsImage(size); + } + else if (CrossPlatformLayout is not null) + { + returnSize = CrossPlatformLayout.CrossPlatformMeasure(size.Width, size.Height).ToCGSize(); + } + else + { + returnSize = child.SizeThatFits(size); + } } - return child.SizeThatFits(size); + CacheMeasureConstraints(size.Width, size.Height); + return returnSize; } - internal CGSize SizeThatFitsWrapper(CGSize originalSpec, double virtualViewWidth, double virtualViewHeight) + internal CGSize SizeThatFitsWrapper(CGSize originalSpec, double virtualViewWidth, double virtualViewHeight, IView view) { var subviews = Subviews; - if (subviews.Length == 0) - return base.SizeThatFits(originalSpec); + CGSize returnSize; + var widthConstraint = IsExplicitSet(virtualViewWidth) ? virtualViewWidth : originalSpec.Width; + var heightConstraint = IsExplicitSet(virtualViewHeight) ? virtualViewHeight : originalSpec.Height; - var child = subviews[0]; + if (subviews.Length == 0) + { + returnSize = base.SizeThatFits(originalSpec); + } - if (child is UIImageView || (child is UIButton imageButton && imageButton.ImageView?.Image is not null && imageButton.CurrentTitle is null)) + else { - var widthConstraint = IsExplicitSet(virtualViewWidth) ? virtualViewWidth : originalSpec.Width; - var heightConstraint = IsExplicitSet(virtualViewHeight) ? virtualViewHeight : originalSpec.Height; - return SizeThatFits(new CGSize(widthConstraint, heightConstraint)); + var child = subviews[0]; + + if (child is UIImageView || (child is UIButton imageButton && imageButton.ImageView?.Image is not null && imageButton.CurrentTitle is null)) + { + if(CrossPlatformLayout is not null) + { + returnSize = CrossPlatformLayout.CrossPlatformMeasure(widthConstraint, heightConstraint); + } + else + { + returnSize = SizeThatFits(new CGSize(widthConstraint, heightConstraint)); + } + } + + else if (CrossPlatformLayout is not null) + { + returnSize = CrossPlatformLayout.CrossPlatformMeasure(widthConstraint, heightConstraint); + } + else + { + returnSize = SizeThatFits(originalSpec); + } } - return SizeThatFits(originalSpec); + CacheMeasureConstraints(widthConstraint, heightConstraint); + return returnSize; } public override void SetNeedsLayout() diff --git a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt index 97a393570318..8f64668820ac 100644 --- a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -49,6 +49,7 @@ Microsoft.Maui.Platform.MauiScrollView Microsoft.Maui.Platform.MauiScrollView.MauiScrollView() -> void Microsoft.Maui.SoftInputExtensions override Microsoft.Maui.Handlers.ButtonHandler.NeedsContainer.get -> bool +override Microsoft.Maui.Handlers.ButtonHandler.SetupContainer() -> void override Microsoft.Maui.Handlers.ContentViewHandler.DisconnectHandler(Microsoft.Maui.Platform.ContentView! platformView) -> void override Microsoft.Maui.Handlers.RefreshViewHandler.SetVirtualView(Microsoft.Maui.IView! view) -> void override Microsoft.Maui.Handlers.ScrollViewHandler.NeedsContainer.get -> bool @@ -159,4 +160,5 @@ Microsoft.Maui.Platform.MauiView.IsMeasureValid(double widthConstraint, double h Microsoft.Maui.Platform.UIEdgeInsetsExtensions static Microsoft.Maui.Platform.UIEdgeInsetsExtensions.ToThickness(this UIKit.UIEdgeInsets insets) -> Microsoft.Maui.Thickness override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void +*REMOVED*override Microsoft.Maui.Platform.MauiLabel.InvalidateIntrinsicContentSize() -> voidrect) -> void *REMOVED*override Microsoft.Maui.Platform.MauiLabel.InvalidateIntrinsicContentSize() -> 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 357339f26868..80d663e778c9 100644 --- a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -50,6 +50,7 @@ Microsoft.Maui.Platform.MauiScrollView Microsoft.Maui.Platform.MauiScrollView.MauiScrollView() -> void Microsoft.Maui.Platform.KeyboardAutoManagerScroll Microsoft.Maui.SoftInputExtensions +override Microsoft.Maui.Handlers.ButtonHandler.SetupContainer() -> void override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void override Microsoft.Maui.Handlers.ButtonHandler.NeedsContainer.get -> bool override Microsoft.Maui.Handlers.ContentViewHandler.DisconnectHandler(Microsoft.Maui.Platform.ContentView! platformView) -> void