Skip to content
Merged
21 changes: 14 additions & 7 deletions src/Controls/src/Core/Handlers/Items/iOS/ItemsViewCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ protected ItemsViewCell(CGRect frame) : base(frame)
}

protected void InitializeContentConstraints(UIView platformView)
{
SetupPlatformView(platformView, true);
}

private protected void SetupPlatformView(UIView platformView, bool autoLayout = false)
{
ContentView.TranslatesAutoresizingMaskIntoConstraints = false;
platformView.TranslatesAutoresizingMaskIntoConstraints = false;

ContentView.AddSubview(platformView);

Expand All @@ -36,12 +40,15 @@ protected void InitializeContentConstraints(UIView platformView)
ContentView.LeadingAnchor.ConstraintEqualTo(LeadingAnchor).Active = true;
ContentView.TrailingAnchor.ConstraintEqualTo(TrailingAnchor).Active = true;

// And we want the ContentView to be the same size as the root renderer for the Forms element
// TODO: we should probably remove this to support `Margin` applied to the cell's root `VirtualView`
ContentView.TopAnchor.ConstraintEqualTo(platformView.TopAnchor).Active = true;
ContentView.BottomAnchor.ConstraintEqualTo(platformView.BottomAnchor).Active = true;
ContentView.LeadingAnchor.ConstraintEqualTo(platformView.LeadingAnchor).Active = true;
ContentView.TrailingAnchor.ConstraintEqualTo(platformView.TrailingAnchor).Active = true;
if (autoLayout)
{
// And we want the ContentView to be the same size as the root renderer for the Forms element
platformView.TranslatesAutoresizingMaskIntoConstraints = false;
ContentView.TopAnchor.ConstraintEqualTo(platformView.TopAnchor).Active = true;
ContentView.BottomAnchor.ConstraintEqualTo(platformView.BottomAnchor).Active = true;
ContentView.LeadingAnchor.ConstraintEqualTo(platformView.LeadingAnchor).Active = true;
ContentView.TrailingAnchor.ConstraintEqualTo(platformView.TrailingAnchor).Active = true;
}
}

public abstract void ConstrainTo(nfloat constant);
Expand Down
99 changes: 89 additions & 10 deletions src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Controls.Internals;
Expand Down Expand Up @@ -200,19 +200,42 @@ public override void ViewWillLayoutSubviews()
{
ConstrainItemsToBounds();

if (CollectionView is Items.MauiCollectionView { NeedsCellLayout: true } collectionView)
var mauiCollectionView = CollectionView as MauiCollectionView;
var needsCellLayout = mauiCollectionView?.NeedsCellLayout is true;
if (needsCellLayout)
{
InvalidateLayoutIfItemsMeasureChanged();
collectionView.NeedsCellLayout = false;
mauiCollectionView.NeedsCellLayout = false;
}

base.ViewWillLayoutSubviews();

if (needsCellLayout || !_laidOut)
{
// We don't want to mess up with ContentOffset while refreshing, given that's also gonna cause
// a change in the content's offset Y.
if (!IsRefreshing())
{
MeasureSupplementaryViews();
LayoutSupplementaryViews();
}
}

InvalidateMeasureIfContentSizeChanged();
LayoutEmptyView();

_laidOut = true;
}

private protected virtual void MeasureSupplementaryViews()
{
RemeasureLayout(_emptyViewFormsElement, _emptyUIView);
}

private protected virtual void LayoutSupplementaryViews()
{
LayoutEmptyView();
}

void InvalidateLayoutIfItemsMeasureChanged()
{
var visibleCells = CollectionView.VisibleCells;
Expand All @@ -237,6 +260,21 @@ void InvalidateLayoutIfItemsMeasureChanged()
}
}

bool IsRefreshing()
{
var subviews = CollectionView.Subviews;
var subviewsLength = subviews.Length;
for (int i = 0; i < subviewsLength; i++)
{
if (subviews[i] is UIRefreshControl { Refreshing: true })
{
return true;
}
}

return false;
}

void MauiCollectionView.ICustomMauiCollectionViewDelegate.MovedToWindow(UIView view)
{
if (CollectionView?.Window != null)
Expand Down Expand Up @@ -549,15 +587,58 @@ protected virtual CGRect DetermineEmptyViewFrame()

protected void RemeasureLayout(VisualElement formsElement)
{
Size size;
if (IsHorizontal)
{
var request = formsElement.Measure(double.PositiveInfinity, CollectionView.Frame.Height);
formsElement.Arrange(new Rect(0, 0, request.Width, CollectionView.Frame.Height));
size = new Size(request.Width, CollectionView.Frame.Height);
}
else
{
var request = formsElement.Measure(CollectionView.Frame.Width, double.PositiveInfinity);
formsElement.Arrange(new Rect(0, 0, CollectionView.Frame.Width, request.Height));
size = new Size(CollectionView.Frame.Width, request.Height);
}

var platformView = formsElement.ToPlatform();
if (platformView.Superview is GeneralWrapperView generalWrapperView)
{
var originalFrame = generalWrapperView.Frame;
generalWrapperView.Frame = new CGRect(originalFrame.X, originalFrame.Y, (nfloat)size.Width, (nfloat)size.Height);
}
else
{
var frame = new Rect(platformView.Frame.X, platformView.Frame.Y, size.Width, size.Height);
formsElement.Arrange(frame);
}
}

void RemeasureLayout(UIView nativeView)
{
var originalFrame = nativeView.Frame;

if (IsHorizontal)
{
var constraints = new CGSize(double.PositiveInfinity, CollectionView.Frame.Height);
var size = nativeView.SizeThatFits(constraints);
nativeView.Frame = new CGRect(originalFrame.X, originalFrame.Y, size.Width, CollectionView.Frame.Height);
}
else
{
var constraints = new CGSize(CollectionView.Frame.Width, double.PositiveInfinity);
var size = nativeView.SizeThatFits(constraints);
nativeView.Frame = new CGRect(originalFrame.X, originalFrame.Y, CollectionView.Frame.Width, size.Height);
}
}

private protected void RemeasureLayout(VisualElement formsElement, UIView nativeElement)
{
if (formsElement is not null)
{
RemeasureLayout(formsElement);
}
else if (nativeElement is not null)
{
RemeasureLayout(nativeElement);
}
}

Expand Down Expand Up @@ -684,7 +765,7 @@ void ShowEmptyView()
ItemsView.AddLogicalChild(_emptyViewFormsElement);
}

LayoutEmptyView();
_emptyUIView.InvalidateMeasure();

AlignEmptyView();
_emptyViewDisplayed = true;
Expand Down Expand Up @@ -723,9 +804,7 @@ void LayoutEmptyView()
var frame = DetermineEmptyViewFrame();

_emptyUIView.Frame = frame;

if (_emptyViewFormsElement != null && ((IElementController)ItemsView).LogicalChildren.IndexOf(_emptyViewFormsElement) != -1)
_emptyViewFormsElement.Layout(frame.ToRectangle());
_emptyViewFormsElement?.Arrange(frame.ToRectangle());
}

TemplatedCell CreateAppropriateCellForLayout()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,25 +74,18 @@ protected override CGRect DetermineEmptyViewFrame()
Math.Abs(CollectionView.Frame.Height - (headerHeight + footerHeight)));
}

public override void ViewWillLayoutSubviews()
private protected override void MeasureSupplementaryViews()
{
var hasHeaderOrFooter = _footerViewFormsElement is not null || _headerViewFormsElement is not null;
if (hasHeaderOrFooter && CollectionView is MauiCollectionView { NeedsCellLayout: true } collectionView)
{
if (_headerViewFormsElement is not null)
{
RemeasureLayout(_headerViewFormsElement);
}
base.MeasureSupplementaryViews();

if (_footerViewFormsElement is not null)
{
RemeasureLayout(_footerViewFormsElement);
}

UpdateHeaderFooterPosition();
}
RemeasureLayout(_headerViewFormsElement, _headerUIView);
RemeasureLayout(_footerViewFormsElement, _footerUIView);
}

base.ViewWillLayoutSubviews();
private protected override void LayoutSupplementaryViews()
{
base.LayoutSupplementaryViews();
UpdateHeaderFooterPosition();
}

internal void UpdateFooterView()
Expand Down Expand Up @@ -131,14 +124,7 @@ internal void UpdateSubview(object view, DataTemplate viewTemplate, nint viewTag
ItemsView.AddLogicalChild(formsElement);
}

if (formsElement != null)
{
RemeasureLayout(formsElement);
}
else
{
uiView?.SizeToFit();
}
RemeasureLayout(formsElement, uiView);
}

void UpdateHeaderFooterPosition()
Expand All @@ -149,22 +135,10 @@ void UpdateHeaderFooterPosition()
{
var currentInset = CollectionView.ContentInset;

nfloat headerWidth = ((ItemsView?.Header is View) ? _headerViewFormsElement?.ToPlatform() : _headerUIView)?.Frame.Width ?? 0f;
nfloat footerWidth = ((ItemsView?.Footer is View) ? _footerViewFormsElement?.ToPlatform() : _footerUIView)?.Frame.Width ?? 0f;
nfloat headerWidth = _headerUIView?.Frame.Width ?? 0f;
nfloat footerWidth = _footerUIView?.Frame.Width ?? 0f;
nfloat emptyWidth = emptyView?.Frame.Width ?? 0f;

if (_headerUIView != null && _headerUIView.Frame.X != headerWidth)
{
_headerUIView.Frame = new CoreGraphics.CGRect(-headerWidth, 0, headerWidth, CollectionView.Frame.Height);
}

if (_footerUIView != null && (_footerUIView.Frame.X != ItemsViewLayout.CollectionViewContentSize.Width || emptyWidth > 0))
{
_footerUIView.Frame = new CoreGraphics.CGRect(
ItemsViewLayout.CollectionViewContentSize.Width + emptyWidth, 0, footerWidth,
CollectionView.Frame.Height);
}

if (CollectionView.ContentInset.Left != headerWidth || CollectionView.ContentInset.Right != footerWidth)
{
var currentOffset = CollectionView.ContentOffset;
Expand All @@ -177,14 +151,31 @@ void UpdateHeaderFooterPosition()

// if the header grows it will scroll off the screen because if you change the content inset iOS adjusts the content offset so the list doesn't move
// this changes the offset of the list by however much the header size has changed
CollectionView.ContentOffset = new CoreGraphics.CGPoint(xOffset, CollectionView.ContentOffset.Y);
CollectionView.ContentOffset = new CGPoint(xOffset, CollectionView.ContentOffset.Y);
}

if (_headerUIView != null && _headerUIView.Frame.X != -headerWidth)
{
_headerUIView.Frame = new CGRect(-headerWidth, 0, headerWidth, CollectionView.Frame.Height);
}

if (_footerUIView != null && IsViewLoaded && View.Window != null)
{
var width = ItemsViewLayout.CollectionViewContentSize.Height;
var footerX = width + emptyWidth;
var currentFrame = _footerUIView.Frame;

if (currentFrame.X != footerX)
{
_footerUIView.Frame = new CGRect(footerX, 0, footerWidth, CollectionView.Frame.Height);
}
}
}
else
{
var currentInset = CollectionView.ContentInset;
nfloat headerHeight = ((ItemsView?.Header is View) ? _headerViewFormsElement?.ToPlatform() : _headerUIView)?.Frame.Height ?? 0f;
nfloat footerHeight = ((ItemsView?.Footer is View) ? _footerViewFormsElement?.ToPlatform() : _footerUIView)?.Frame.Height ?? 0f;
nfloat headerHeight = _headerUIView?.Frame.Height ?? 0f;
nfloat footerHeight = _footerUIView?.Frame.Height ?? 0f;
nfloat emptyHeight = emptyView?.Frame.Height ?? 0f;

if (CollectionView.ContentInset.Top != headerHeight || CollectionView.ContentInset.Bottom != footerHeight)
Expand All @@ -202,25 +193,25 @@ void UpdateHeaderFooterPosition()

if (currentOffset.Y.Value < headerHeight)
{
CollectionView.ContentOffset = new CoreGraphics.CGPoint(CollectionView.ContentOffset.X, yOffset);
CollectionView.ContentOffset = new CGPoint(CollectionView.ContentOffset.X, yOffset);
}
}

if (_headerUIView != null && (_headerUIView.Frame.Y != -headerHeight || _headerUIView.Frame.Width != CollectionView.Frame.Width))
if (_headerUIView != null && _headerUIView.Frame.Y != -headerHeight)
{
_headerUIView.Frame = new CoreGraphics.CGRect(0, -headerHeight, CollectionView.Frame.Width, headerHeight);
_headerUIView.Frame = new CGRect(0, -headerHeight, CollectionView.Frame.Width, headerHeight);
}

nfloat height = 0;

if (IsViewLoaded && View.Window != null)
if (_footerUIView != null && IsViewLoaded && View.Window != null)
{
height = ItemsViewLayout.CollectionViewContentSize.Height;
}
var height = ItemsViewLayout.CollectionViewContentSize.Height;
var footerY = height + emptyHeight;
var currentFrame = _footerUIView.Frame;

if (_footerUIView != null && (_footerUIView.Frame.Y != height || emptyHeight > 0 || _footerUIView.Frame.Height != footerHeight || _footerUIView.Frame.Width != CollectionView.Frame.Width))
{
_footerUIView.Frame = new CoreGraphics.CGRect(0, height + emptyHeight, CollectionView.Frame.Width, footerHeight);
if (currentFrame.Y != footerY)
{
_footerUIView.Frame = new CGRect(0, footerY, CollectionView.Frame.Width, footerHeight);
}
}
}
}
Expand Down
Loading
Loading