diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutContentRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutContentRenderer.cs index 40f20cc7123a..d3614f3dd458 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutContentRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutContentRenderer.cs @@ -1,6 +1,10 @@ #nullable disable using System; using System.ComponentModel; +using CoreGraphics; +using Microsoft.Maui.Controls.Platform; +using Microsoft.Maui.Graphics; +using ObjCRuntime; using UIKit; namespace Microsoft.Maui.Controls.Platform.Compatibility @@ -131,11 +135,12 @@ void UpdateFlyoutFooter(View view) if (_footer is not null) { - _footerView = new ShellFlyoutFooterContainer(_footer); + _footerView = new UIContainerView(_footer); _uIViews[FooterIndex] = _footerView; AddViewInCorrectOrder(_footerView, previousIndex); _footerView.ClipsToBounds = true; + _footerView.PlatformMeasureInvalidated += OnFooterMeasureInvalidated; } _tableViewController.FooterView = _footerView; @@ -181,10 +186,45 @@ void AddViewInCorrectOrder(UIView newView, int previousIndex) View.AddSubview(newView); } + void OnFooterMeasureInvalidated(object sender, System.EventArgs e) + { + ReMeasureFooter(); + } + + void ReMeasureFooter() + { + var size = _footerView?.SizeThatFits(new CGSize(View.Frame.Width, double.PositiveInfinity)); + if (size is not null) + UpdateFooterPosition(size.Value.Height); + } + + void UpdateFooterPosition() + { + if (_footerView is null) + return; + + if (double.IsNaN(_footerView.MeasuredHeight)) + ReMeasureFooter(); + else + UpdateFooterPosition((nfloat)_footerView.MeasuredHeight); + } + + void UpdateFooterPosition(nfloat footerHeight) + { + if (_footerView is null && !nfloat.IsNaN(footerHeight)) + return; + + var footerWidth = View.Frame.Width; + + _footerView.Frame = new CoreGraphics.CGRect(0, View.Frame.Height - footerHeight, footerWidth, footerHeight); + + _tableViewController.LayoutParallax(); + } + public override void ViewWillLayoutSubviews() { base.ViewWillLayoutSubviews(); - _tableViewController.LayoutParallax(); + UpdateFooterPosition(); UpdateFlyoutContent(); } @@ -273,6 +313,11 @@ public override void ViewDidLayoutSubviews() _bgImage.Frame = View.Bounds; } + public override void LoadView() + { + View = new UIContainerViewContainer(); + } + public override void ViewDidLoad() { base.ViewDidLoad(); @@ -317,7 +362,7 @@ void UpdateFlyoutContent() _uIViews[ContentIndex] = _shellFlyoutContentManager.ContentView; AddViewInCorrectOrder(_uIViews[ContentIndex], previousIndex); - _shellFlyoutContentManager.UpdateHeaderSize(); + _shellFlyoutContentManager.UpdateLayout(); } public override void ViewWillAppear(bool animated) diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutFooterContainer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutFooterContainer.cs deleted file mode 100644 index a20f6385793e..000000000000 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutFooterContainer.cs +++ /dev/null @@ -1,34 +0,0 @@ -using CoreGraphics; - -namespace Microsoft.Maui.Controls.Platform.Compatibility; - -internal class ShellFlyoutFooterContainer : UIContainerView, IPlatformMeasureInvalidationController -{ - bool _invalidateParentWhenMovedToWindow; - - public ShellFlyoutFooterContainer(View view) : base(view) { } - - void IPlatformMeasureInvalidationController.InvalidateAncestorsMeasuresWhenMovedToWindow() - { - _invalidateParentWhenMovedToWindow = true; - } - - void IPlatformMeasureInvalidationController.InvalidateMeasure(bool isPropagating) - { - if (Superview is not null) - { - var size = SizeThatFits(new CGSize(Superview.Frame.Width, double.PositiveInfinity)); - Frame = new CGRect(0, Superview.Frame.Height - size.Height, Superview.Frame.Width, size.Height); - } - } - - public override void MovedToWindow() - { - base.MovedToWindow(); - if (_invalidateParentWhenMovedToWindow) - { - _invalidateParentWhenMovedToWindow = false; - this.InvalidateAncestorsMeasures(); - } - } -} diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutHeaderContainer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutHeaderContainer.cs index 316f24d4cb3e..875cb30c1555 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutHeaderContainer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutHeaderContainer.cs @@ -34,21 +34,13 @@ public override Thickness Margin } } - public override void LayoutSubviews() - { - if (!UpdateSafeAreaMargin()) - base.LayoutSubviews(); - - OnHeaderSizeChanged(); - } - public override void SafeAreaInsetsDidChange() { UpdateSafeAreaMargin(); base.SafeAreaInsetsDidChange(); } - bool UpdateSafeAreaMargin() + void UpdateSafeAreaMargin() { var safeArea = UIApplication.SharedApplication.GetSafeAreaInsetsForWindow(); @@ -64,12 +56,8 @@ bool UpdateSafeAreaMargin() safeArea.Right, safeArea.Bottom); - OnHeaderSizeChanged(); - return true; + OnPlatformMeasureInvalidated(); } - - return false; - } } } diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutLayoutManager.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutLayoutManager.cs index 191cd98b3104..a92226d69f1a 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutLayoutManager.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutLayoutManager.cs @@ -16,7 +16,7 @@ class ShellFlyoutLayoutManager UIView _contentView; UIScrollView ScrollView { get; set; } UIContainerView _headerView; - UIView _footerView; + UIContainerView _footerView; double _headerSize; readonly IShellContext _context; Action removeScrolledEvent; @@ -142,7 +142,7 @@ private set ScrollView.ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Never; } - UpdateHeaderSize(); + UpdateLayout(); } } @@ -155,18 +155,18 @@ public virtual UIContainerView HeaderView return; if (_headerView is not null) - _headerView.HeaderSizeChanged -= OnHeaderViewMeasureChanged; + _headerView.PlatformMeasureInvalidated -= OnHeaderViewMeasureChanged; _headerView = value; if (_headerView is not null) - _headerView.HeaderSizeChanged += OnHeaderViewMeasureChanged; + _headerView.PlatformMeasureInvalidated += OnHeaderViewMeasureChanged; - UpdateHeaderSize(); + UpdateLayout(); } } - public virtual UIView FooterView + public virtual UIContainerView FooterView { get => _footerView; set @@ -174,21 +174,42 @@ public virtual UIView FooterView if (_footerView == value) return; + if (_footerView is not null) + _footerView.PlatformMeasureInvalidated -= OnFooterViewMeasureChanged; + _footerView = value; - UpdateHeaderSize(); + + if (_footerView is not null) + _footerView.PlatformMeasureInvalidated += OnFooterViewMeasureChanged; + + UpdateLayout(); } } + void OnFooterViewMeasureChanged(object sender, EventArgs e) + { + if (FooterView is null || ContentView?.Superview is not { } flyoutView) + return; + + var flyoutFrame = flyoutView.Frame; + var widthConstraint = flyoutFrame.Width; + var heightConstraint = flyoutFrame.Height; + var size = FooterView.SizeThatFits(new CGSize(widthConstraint, double.PositiveInfinity)); + FooterView.Frame = new CGRect(0, heightConstraint - size.Height, widthConstraint, size.Height); + + UpdateLayout(); + } + void OnHeaderViewMeasureChanged(object sender, EventArgs e) { if (HeaderView is null || ContentView?.Superview is null) return; HeaderView.SizeThatFits(new CGSize(ContentView.Superview.Frame.Width, double.PositiveInfinity)); - UpdateHeaderSize(); + UpdateLayout(); } - internal void UpdateHeaderSize() + internal void UpdateLayout() { if (HeaderView is null || ContentView?.Superview is null) return; @@ -365,7 +386,7 @@ void OnShellPropertyChanged(object sender, PropertyChangedEventArgs e) public void ViewDidLoad() { - UpdateHeaderSize(); + UpdateLayout(); } public void OnScrolled(nfloat contentOffsetY) diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellTableViewController.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellTableViewController.cs index ffe437b1aa73..d3ef1ed4bc88 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellTableViewController.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellTableViewController.cs @@ -51,10 +51,11 @@ public virtual UIContainerView HeaderView set => ShellFlyoutContentManager.HeaderView = value; } + // TODO: .NET10 change to UIContainerView public virtual UIView FooterView { get => ShellFlyoutContentManager.FooterView; - set => ShellFlyoutContentManager.FooterView = value; + set => ShellFlyoutContentManager.FooterView = value as UIContainerView; } protected ShellTableViewSource CreateShellTableViewSource() diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellTableViewSource.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellTableViewSource.cs index 599737f33157..6f9f59412722 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellTableViewSource.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellTableViewSource.cs @@ -178,16 +178,10 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index _cells[context] = cell; cell.TableView = tableView; cell.IndexPath = indexPath; - cell.ViewMeasureInvalidated += OnViewMeasureInvalidated; return cell; } - void OnViewMeasureInvalidated(UIContainerCell cell) - { - cell.ReloadRow(); - } - public override nfloat GetHeightForFooter(UITableView tableView, nint section) { if (section < Groups.Count - 1) diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerCell.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerCell.cs index ad0ec77225cd..204e256d2761 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerCell.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerCell.cs @@ -6,19 +6,17 @@ namespace Microsoft.Maui.Controls.Platform.Compatibility { - public class UIContainerCell : UITableViewCell + public class UIContainerCell : UITableViewCell, IPlatformMeasureInvalidationController { IPlatformViewHandler _renderer; object _bindingContext; - internal Action ViewMeasureInvalidated { get; set; } internal NSIndexPath IndexPath { get; set; } internal UITableView TableView { get; set; } internal UIContainerCell(string cellId, View view, Shell shell, object context) : base(UITableViewCellStyle.Default, cellId) { View = view; - View.MeasureInvalidated += MeasureInvalidated; SelectionStyle = UITableViewCellSelectionStyle.None; _renderer = (IPlatformViewHandler)view.Handler; @@ -43,16 +41,20 @@ internal UIContainerCell(string cellId, View view, Shell shell, object context) shell.AddLogicalChild(View); } - public UIContainerCell(string cellId, View view) : this(cellId, view, null, null) + void IPlatformMeasureInvalidationController.InvalidateAncestorsMeasuresWhenMovedToWindow() { } - void MeasureInvalidated(object sender, System.EventArgs e) + void IPlatformMeasureInvalidationController.InvalidateMeasure(bool isPropagating) { - if (View == null || TableView == null) - return; + if (isPropagating) + { + ReloadRow(); + } + } - ViewMeasureInvalidated?.Invoke(this); + public UIContainerCell(string cellId, View view) : this(cellId, view, null, null) + { } internal void ReloadRow() @@ -62,8 +64,6 @@ internal void ReloadRow() internal void Disconnect(Shell shell = null, bool keepRenderer = false) { - ViewMeasureInvalidated = null; - View.MeasureInvalidated -= MeasureInvalidated; if (_bindingContext != null && _bindingContext is BaseShellItem baseShell) baseShell.PropertyChanged -= OnElementPropertyChanged; diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerView.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerView.cs index 8791278dd497..fd78de7dbcc4 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerView.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerView.cs @@ -7,15 +7,17 @@ namespace Microsoft.Maui.Controls.Platform.Compatibility { - public class UIContainerView : UIView + public class UIContainerView : UIView, IPlatformMeasureInvalidationController { readonly View _view; + bool _invalidateParentWhenMovedToWindow; + bool _measureInvalidated; IPlatformViewHandler _renderer; UIView _platformView; bool _disposed; double _measuredHeight; - internal event EventHandler HeaderSizeChanged; + internal event EventHandler PlatformMeasureInvalidated; public UIContainerView(View view) { @@ -78,9 +80,9 @@ public virtual Thickness Margin get; } - private protected void OnHeaderSizeChanged() + private protected void OnPlatformMeasureInvalidated() { - HeaderSizeChanged?.Invoke(this, EventArgs.Empty); + PlatformMeasureInvalidated?.Invoke(this, EventArgs.Empty); } public override CGSize SizeThatFits(CGSize size) @@ -156,5 +158,34 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + + void IPlatformMeasureInvalidationController.InvalidateAncestorsMeasuresWhenMovedToWindow() + { + _invalidateParentWhenMovedToWindow = true; + } + + void IPlatformMeasureInvalidationController.InvalidateMeasure(bool isPropagating) + { + _measureInvalidated = true; + } + + public override void MovedToWindow() + { + base.MovedToWindow(); + if (_invalidateParentWhenMovedToWindow) + { + _invalidateParentWhenMovedToWindow = false; + this.InvalidateAncestorsMeasures(); + } + } + + internal void NotifyMeasureInvalidated() + { + if (_measureInvalidated) + { + _measureInvalidated = false; + OnPlatformMeasureInvalidated(); + } + } } } diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerViewContainer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerViewContainer.cs new file mode 100644 index 000000000000..c8d138d548b5 --- /dev/null +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/UIContainerViewContainer.cs @@ -0,0 +1,20 @@ +using UIKit; + +namespace Microsoft.Maui.Controls.Platform.Compatibility; + +internal class UIContainerViewContainer : UIView +{ + public override void LayoutSubviews() + { + base.LayoutSubviews(); + + var subviews = Subviews; + for (int i = 0; i < subviews.Length; i++) + { + if (subviews[i] is UIContainerView containerView) + { + containerView.NotifyMeasureInvalidated(); + } + } + } +} \ No newline at end of file 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 63a6c7ddd87a..573825e1be8e 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -3,6 +3,8 @@ Microsoft.Maui.Controls.StyleableElement.Style.get -> Microsoft.Maui.Controls.St override Microsoft.Maui.Controls.Handlers.Items.TemplatedCell.LayoutSubviews() -> void override Microsoft.Maui.Controls.Handlers.Items2.TemplatedCell2.LayoutSubviews() -> void override Microsoft.Maui.Controls.Handlers.Items2.ItemsViewHandler2.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size +override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutContentRenderer.LoadView() -> void +override Microsoft.Maui.Controls.Platform.Compatibility.UIContainerView.MovedToWindow() -> void ~abstract Microsoft.Maui.Controls.Handlers.Items2.ItemsViewHandler2.CreateController(TItemsView newElement, UIKit.UICollectionViewLayout layout) -> Microsoft.Maui.Controls.Handlers.Items2.ItemsViewController2 ~abstract Microsoft.Maui.Controls.Handlers.Items2.ItemsViewHandler2.SelectLayout() -> UIKit.UICollectionViewLayout *REMOVED*~Microsoft.Maui.Controls.Handlers.Compatibility.ShellScrollViewTracker.ShellScrollViewTracker(Microsoft.Maui.IPlatformViewHandler renderer) -> 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 4d313d1b0c85..21e123c963e1 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -3,6 +3,8 @@ Microsoft.Maui.Controls.StyleableElement.Style.get -> Microsoft.Maui.Controls.St override Microsoft.Maui.Controls.Handlers.Items.TemplatedCell.LayoutSubviews() -> void override Microsoft.Maui.Controls.Handlers.Items2.TemplatedCell2.LayoutSubviews() -> void override Microsoft.Maui.Controls.Handlers.Items2.ItemsViewHandler2.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size +override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutContentRenderer.LoadView() -> void +override Microsoft.Maui.Controls.Platform.Compatibility.UIContainerView.MovedToWindow() -> void ~abstract Microsoft.Maui.Controls.Handlers.Items2.ItemsViewHandler2.CreateController(TItemsView newElement, UIKit.UICollectionViewLayout layout) -> Microsoft.Maui.Controls.Handlers.Items2.ItemsViewController2 ~abstract Microsoft.Maui.Controls.Handlers.Items2.ItemsViewHandler2.SelectLayout() -> UIKit.UICollectionViewLayout *REMOVED*~Microsoft.Maui.Controls.Handlers.Compatibility.ShellScrollViewTracker.ShellScrollViewTracker(Microsoft.Maui.IPlatformViewHandler renderer) -> void