File: iOS\CollectionView\GroupableItemsViewController.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using UIKit;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
{
	[System.Obsolete]
	public class GroupableItemsViewController<TItemsView> : SelectableItemsViewController<TItemsView>
		where TItemsView : GroupableItemsView
	{
		// Keep a cached value for the current state of grouping around so we can avoid hitting the 
		// BindableProperty all the time 
		bool _isGrouped;
 
		// Keep out header measurement cells for iOS handy so we don't have to
		// create new ones all the time. For other versions, the reusable cells
		// queueing mechanism does this for us.
		TemplatedCell _measurementCellTemplated;
		DefaultCell _measurementCellDefault;
 
		Action _scrollAnimationEndedCallback;
 
		public GroupableItemsViewController(TItemsView groupableItemsView, ItemsViewLayout layout)
			: base(groupableItemsView, layout)
		{
			_isGrouped = ItemsView.IsGrouped;
		}
 
		protected override UICollectionViewDelegateFlowLayout CreateDelegator()
		{
			return new GroupableItemsViewDelegator<TItemsView, GroupableItemsViewController<TItemsView>>(ItemsViewLayout, this);
		}
 
		protected override IItemsViewSource CreateItemsViewSource()
		{
			// Use the BindableProperty here (instead of _isGroupingEnabled) because the cached value might not be set yet
			if (ItemsView.IsGrouped)
			{
				return ItemsSourceFactory.CreateGrouped(ItemsView.ItemsSource, this);
			}
 
			return base.CreateItemsViewSource();
		}
 
		public override void UpdateItemsSource()
		{
			_isGrouped = ItemsView.IsGrouped;
			base.UpdateItemsSource();
		}
 
		protected override void RegisterViewTypes()
		{
			base.RegisterViewTypes();
 
			RegisterSupplementaryViews(UICollectionElementKindSection.Header);
			RegisterSupplementaryViews(UICollectionElementKindSection.Footer);
		}
 
		void RegisterSupplementaryViews(UICollectionElementKindSection kind)
		{
			CollectionView.RegisterClassForSupplementaryView(typeof(HorizontalSupplementaryView),
				kind, HorizontalSupplementaryView.ReuseId);
			CollectionView.RegisterClassForSupplementaryView(typeof(VerticalSupplementaryView),
				kind, VerticalSupplementaryView.ReuseId);
			CollectionView.RegisterClassForSupplementaryView(typeof(HorizontalDefaultSupplementalView),
				kind, HorizontalDefaultSupplementalView.ReuseId);
			CollectionView.RegisterClassForSupplementaryView(typeof(VerticalDefaultSupplementalView),
				kind, VerticalDefaultSupplementalView.ReuseId);
		}
 
		public override UICollectionReusableView GetViewForSupplementaryElement(UICollectionView collectionView,
			NSString elementKind, NSIndexPath indexPath)
		{
			var reuseId = DetermineViewReuseId(elementKind);
 
			var view = collectionView.DequeueReusableSupplementaryView(elementKind, reuseId, indexPath) as UICollectionReusableView;
 
			switch (view)
			{
				case DefaultCell defaultCell:
					UpdateDefaultSupplementaryView(defaultCell, elementKind, indexPath);
					break;
				case TemplatedCell templatedCell:
					UpdateTemplatedSupplementaryView(templatedCell, elementKind, indexPath);
					break;
			}
 
			return view;
		}
 
		void UpdateDefaultSupplementaryView(DefaultCell cell, NSString elementKind, NSIndexPath indexPath)
		{
			cell.Label.Text = ItemsSource.Group(indexPath).ToString();
 
			if (cell is ItemsViewCell)
			{
				cell.ConstrainTo(GetLayoutSpanCount() * ItemsViewLayout.ConstrainedDimension);
			}
		}
 
		void UpdateTemplatedSupplementaryView(TemplatedCell cell, NSString elementKind, NSIndexPath indexPath)
		{
			DataTemplate template = elementKind == UICollectionElementKindSectionKey.Header
				? ItemsView.GroupHeaderTemplate
				: ItemsView.GroupFooterTemplate;
 
			var bindingContext = ItemsSource.Group(indexPath);
 
			cell.Bind(template, bindingContext, ItemsView);
 
			if (cell is ItemsViewCell)
			{
				cell.ConstrainTo(GetLayoutSpanCount() * ItemsViewLayout.ConstrainedDimension);
			}
		}
 
		string DetermineViewReuseId(NSString elementKind)
		{
			return DetermineViewReuseId(elementKind == UICollectionElementKindSectionKey.Header
				? ItemsView.GroupHeaderTemplate
				: ItemsView.GroupFooterTemplate);
		}
 
		string DetermineViewReuseId(DataTemplate template)
		{
			if (template == null)
			{
				// No template, fall back the the default supplemental views
				return ItemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Horizontal
					? HorizontalDefaultSupplementalView.ReuseId
					: VerticalDefaultSupplementalView.ReuseId;
			}
 
			return ItemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Horizontal
				? HorizontalSupplementaryView.ReuseId
				: VerticalSupplementaryView.ReuseId;
		}
 
		internal CGSize GetReferenceSizeForHeader(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
		{
			if (!_isGrouped)
			{
				return CGSize.Empty;
			}
 
			// Currently we explicitly measure all of the headers/footers 
			// Long-term, we might want to look at performance hints (similar to ItemSizingStrategy) for 
			// headers/footers (if the dev knows for sure they'll all the be the same size)
			return GetReferenceSizeForheaderOrFooter(collectionView, ItemsView.GroupHeaderTemplate, UICollectionElementKindSectionKey.Header, section);
		}
 
		internal CGSize GetReferenceSizeForFooter(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
		{
			if (!_isGrouped)
			{
				return CGSize.Empty;
			}
 
			return GetReferenceSizeForheaderOrFooter(collectionView, ItemsView.GroupFooterTemplate, UICollectionElementKindSectionKey.Footer, section);
		}
 
		internal CGSize GetReferenceSizeForheaderOrFooter(UICollectionView collectionView, DataTemplate template, NSString elementKind, nint section)
		{
			if (!_isGrouped || template == null)
			{
				return CGSize.Empty;
			}
 
			if (ItemsSource.GroupCount < 1 || section > ItemsSource.GroupCount - 1)
			{
				return CGSize.Empty;
			}
 
			if (!Forms.IsiOS11OrNewer)
			{
				// iOS 10 crashes if we try to dequeue a cell for measurement
				// so we'll use an alternate method
				return MeasureSupplementaryView(elementKind, section);
			}
 
			var cell = GetViewForSupplementaryElement(collectionView, elementKind,
				NSIndexPath.FromItemSection(0, section)) as ItemsViewCell;
 
			return cell.Measure();
		}
 
		internal void SetScrollAnimationEndedCallback(Action callback)
		{
			_scrollAnimationEndedCallback = callback;
		}
 
		internal void HandleScrollAnimationEnded()
		{
			_scrollAnimationEndedCallback?.Invoke();
			_scrollAnimationEndedCallback = null;
		}
 
		int GetLayoutSpanCount()
		{
			var span = 1;
 
			if (ItemsView?.ItemsLayout is GridItemsLayout gridItemsLayout)
			{
				span = gridItemsLayout.Span;
			}
 
			return span;
		}
 
		internal UIEdgeInsets GetInsetForSection(ItemsViewLayout itemsViewLayout,
			UICollectionView collectionView, nint section)
		{
			var uIEdgeInsets = ItemsViewLayout.GetInsetForSection(collectionView, itemsViewLayout, section);
 
			if (!ItemsView.IsGrouped)
			{
				return uIEdgeInsets;
			}
 
			// If we're grouping, we'll need to inset the sections to maintain the spacing between the 
			// groups and their group headers/footers
 
			nfloat spacing = itemsViewLayout.GetMinimumLineSpacingForSection(collectionView, itemsViewLayout, section);
 
			var top = uIEdgeInsets.Top;
			var left = uIEdgeInsets.Left;
 
			if (itemsViewLayout.ScrollDirection == UICollectionViewScrollDirection.Horizontal)
			{
				left += spacing;
			}
			else
			{
				top += spacing;
			}
 
			return new UIEdgeInsets(top, left,
				uIEdgeInsets.Bottom, uIEdgeInsets.Right);
		}
 
		// These measurement methods are only necessary for iOS 10 and lower
		CGSize MeasureTemplatedSupplementaryCell(NSString elementKind, nint section, NSString reuseId)
		{
			if (_measurementCellTemplated == null)
			{
				if (reuseId == HorizontalSupplementaryView.ReuseId)
				{
					_measurementCellTemplated = new HorizontalSupplementaryView(CGRect.Empty);
				}
				else if (reuseId == VerticalSupplementaryView.ReuseId)
				{
					_measurementCellTemplated = new VerticalSupplementaryView(CGRect.Empty);
				}
			}
 
			if (_measurementCellTemplated == null)
			{
				return CGSize.Empty;
			}
 
			UpdateTemplatedSupplementaryView(_measurementCellTemplated, elementKind, NSIndexPath.FromItemSection(0, section));
			return _measurementCellTemplated.Measure();
		}
 
		CGSize MeasureDefaultSupplementaryCell(NSString elementKind, nint section, NSString reuseId)
		{
			if (_measurementCellDefault == null)
			{
				if (reuseId == HorizontalDefaultSupplementalView.ReuseId)
				{
					_measurementCellDefault = new HorizontalDefaultSupplementalView(CGRect.Empty);
				}
				else if (reuseId == VerticalDefaultSupplementalView.ReuseId)
				{
					_measurementCellDefault = new VerticalDefaultSupplementalView(CGRect.Empty);
				}
			}
 
			if (_measurementCellDefault == null)
			{
				return CGSize.Empty;
			}
 
			UpdateDefaultSupplementaryView(_measurementCellDefault, elementKind, NSIndexPath.FromItemSection(0, section));
			return _measurementCellDefault.Measure();
		}
 
		CGSize MeasureSupplementaryView(NSString elementKind, nint section)
		{
			var reuseId = (NSString)DetermineViewReuseId(elementKind);
 
			if (reuseId == HorizontalDefaultSupplementalView.ReuseId
				|| reuseId == VerticalDefaultSupplementalView.ReuseId)
			{
				return MeasureDefaultSupplementaryCell(elementKind, section, reuseId);
			}
 
			return MeasureTemplatedSupplementaryCell(elementKind, section, reuseId);
		}
 
		// end of iOS 10 workaround stuff
	}
}