File: Compatibility\Handlers\Shell\iOS\ShellSectionRootHeader.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Graphics;
using ObjCRuntime;
using UIKit;
using static Microsoft.Maui.Controls.Compatibility.Platform.iOS.AccessibilityExtensions;
 
namespace Microsoft.Maui.Controls.Platform.Compatibility
{
	public class ShellSectionRootHeader : UICollectionViewController, IAppearanceObserver, IShellSectionRootHeader
	{
		#region IAppearanceObserver
 
		Color _defaultBackgroundColor = new Color(0.964f);
		Color _defaultForegroundColor = Colors.Black;
		Color _defaultUnselectedColor = Colors.Black.MultiplyAlpha(0.7f);
 
		void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance)
		{
			if (appearance == null)
				ResetAppearance();
			else
				SetAppearance(appearance);
		}
 
		protected virtual void ResetAppearance()
		{
			SetValues(_defaultBackgroundColor, _defaultForegroundColor, _defaultUnselectedColor);
		}
 
		protected virtual void SetAppearance(ShellAppearance appearance)
		{
			SetValues(appearance.BackgroundColor ?? _defaultBackgroundColor,
				appearance.ForegroundColor ?? _defaultForegroundColor,
				appearance.UnselectedColor ?? _defaultUnselectedColor);
		}
 
		void SetValues(Color backgroundColor, Color foregroundColor, Color unselectedColor)
		{
			CollectionView.BackgroundColor = new Color(backgroundColor.Red, backgroundColor.Green, backgroundColor.Blue, .863f).ToPlatform();
 
			bool reloadData = _selectedColor != foregroundColor || _unselectedColor != unselectedColor;
 
			_selectedColor = foregroundColor;
			_unselectedColor = unselectedColor;
 
			if (reloadData)
				ReloadData();
		}
 
		#endregion IAppearanceObserver
 
		static readonly NSString CellId = new NSString("HeaderCell");
 
		readonly IShellContext _shellContext;
		UIView _bar;
		UIView _bottomShadow;
		Color _selectedColor;
		Color _unselectedColor;
		bool _isDisposed;
 
		[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
		public ShellSectionRootHeader()
		{
 
		}
 
		[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
		public ShellSectionRootHeader(IShellContext shellContext) : base(new UICollectionViewFlowLayout())
		{
			_shellContext = shellContext;
		}
 
		public double SelectedIndex { get; set; }
		public ShellSection ShellSection { get; set; }
		IShellSectionController ShellSectionController => ShellSection;
 
		public UIViewController ViewController => this;
 
		public override bool CanMoveItem(UICollectionView collectionView, NSIndexPath indexPath)
		{
			return false;
		}
 
		public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
		{
			var reusedCell = (UICollectionViewCell)collectionView.DequeueReusableCell(CellId, indexPath);
			var headerCell = reusedCell as ShellSectionHeaderCell;
 
			if (headerCell == null)
				return reusedCell;
 
			var selectedItems = collectionView.GetIndexPathsForSelectedItems();
 
			var shellContent = ShellSectionController.GetItems()[indexPath.Row];
			headerCell.Label.Text = shellContent.Title;
			headerCell.Label.SetNeedsDisplay();
 
			headerCell.SelectedColor = _selectedColor.ToPlatform();
			headerCell.UnSelectedColor = _unselectedColor.ToPlatform();
 
			if (selectedItems.Length > 0 && selectedItems[0].Row == indexPath.Row)
				headerCell.Selected = true;
			else
				headerCell.Selected = false;
 
			headerCell.SetAccessibilityProperties(shellContent);
			return headerCell;
		}
 
		public override nint GetItemsCount(UICollectionView collectionView, nint section)
		{
			return ShellSectionController.GetItems().Count;
		}
 
		public override void ItemDeselected(UICollectionView collectionView, NSIndexPath indexPath)
		{
			if (CollectionView.CellForItem(indexPath) is ShellSectionHeaderCell cell)
				cell.Label.TextColor = _unselectedColor.ToPlatform();
		}
 
		public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath)
		{
			var row = indexPath.Row;
 
			var item = ShellSectionController.GetItems()[row];
 
			if (item != ShellSection.CurrentItem)
				ShellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, item);
 
			if (CollectionView.CellForItem(indexPath) is ShellSectionHeaderCell cell)
				cell.Label.TextColor = _selectedColor.ToPlatform();
		}
 
		public override nint NumberOfSections(UICollectionView collectionView)
		{
			return 1;
		}
 
		public override bool ShouldSelectItem(UICollectionView collectionView, NSIndexPath indexPath)
		{
			var row = indexPath.Row;
			var item = ShellSectionController.GetItems()[row];
			IShellController shellController = _shellContext.Shell;
 
			if (item == ShellSection.CurrentItem)
				return true;
			return shellController.ProposeNavigation(ShellNavigationSource.ShellContentChanged, (ShellItem)ShellSection.Parent, ShellSection, item, ShellSection.Stack, true);
		}
 
		public override void ViewDidLayoutSubviews()
		{
			if (_isDisposed)
				return;
 
			base.ViewDidLayoutSubviews();
 
			LayoutBar();
 
			_bottomShadow.Frame = new CGRect(0, CollectionView.Frame.Bottom, CollectionView.Frame.Width, 0.5);
		}
 
		public override void ViewDidLoad()
		{
			if (_isDisposed)
				return;
 
			base.ViewDidLoad();
 
			CollectionView.ScrollsToTop = false;
			CollectionView.Bounces = false;
			CollectionView.AlwaysBounceHorizontal = false;
			CollectionView.ShowsHorizontalScrollIndicator = false;
			CollectionView.ClipsToBounds = false;
 
			_bar = new UIView(new CGRect(0, 0, 20, 20));
			_bar.BackgroundColor = UIColor.White;
			_bar.Layer.ZPosition = 9001; //its over 9000!
			CollectionView.AddSubview(_bar);
 
			_bottomShadow = new UIView(new CGRect(0, 0, 10, 1));
			_bottomShadow.BackgroundColor = Colors.Black.MultiplyAlpha(0.3f).ToPlatform();
			_bottomShadow.Layer.ZPosition = 9002;
			CollectionView.AddSubview(_bottomShadow);
 
			var flowLayout = Layout as UICollectionViewFlowLayout;
			flowLayout.ScrollDirection = UICollectionViewScrollDirection.Horizontal;
			flowLayout.MinimumInteritemSpacing = 0;
			flowLayout.MinimumLineSpacing = 0;
			flowLayout.EstimatedItemSize = new CGSize(70, 35);
 
			CollectionView.RegisterClassForCell(GetCellType(), CellId);
 
			((IShellController)_shellContext.Shell).AddAppearanceObserver(this, ShellSection);
			ShellSectionController.ItemsCollectionChanged += OnShellSectionItemsChanged;
 
			UpdateSelectedIndex();
			ShellSection.PropertyChanged += OnShellSectionPropertyChanged;
		}
 
		protected virtual Type GetCellType()
		{
			return typeof(ShellSectionHeaderCell);
		}
 
		protected override void Dispose(bool disposing)
		{
			if (_isDisposed)
				return;
 
			if (disposing)
			{
				((IShellController)_shellContext.Shell).RemoveAppearanceObserver(this);
				ShellSectionController.ItemsCollectionChanged -= OnShellSectionItemsChanged;
				ShellSection.PropertyChanged -= OnShellSectionPropertyChanged;
 
				ShellSection = null;
				_bar.RemoveFromSuperview();
				this.RemoveFromParentViewController();
				_bar.Dispose();
				_bar = null;
			}
 
			_isDisposed = true;
			base.Dispose(disposing);
		}
 
		protected void LayoutBar()
		{
			if (SelectedIndex < 0)
				return;
 
			if (ShellSectionController.GetItems().IndexOf(ShellSection.CurrentItem) != SelectedIndex)
				return;
 
			var layout = CollectionView.GetLayoutAttributesForItem(NSIndexPath.FromItemSection((int)SelectedIndex, 0));
 
			if (layout == null)
				return;
 
			var frame = layout.Frame;
 
			if (_bar.Frame.Height != 2)
			{
				_bar.Frame = new CGRect(frame.X, frame.Bottom - 2, frame.Width, 2);
			}
			else
			{
				UIView.Animate(.25, () => _bar.Frame = new CGRect(frame.X, frame.Bottom - 2, frame.Width, 2));
			}
		}
 
		protected virtual void OnShellSectionPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == ShellSection.CurrentItemProperty.PropertyName)
			{
				UpdateSelectedIndex();
			}
		}
 
		protected virtual void UpdateSelectedIndex(bool animated = false)
		{
			if (ShellSection.CurrentItem == null)
				return;
 
			SelectedIndex = ShellSectionController.GetItems().IndexOf(ShellSection.CurrentItem);
 
			if (SelectedIndex < 0)
				return;
 
			LayoutBar();
 
			CollectionView.SelectItem(NSIndexPath.FromItemSection((int)SelectedIndex, 0), false, UICollectionViewScrollPosition.CenteredHorizontally);
		}
 
		void OnShellSectionItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			ReloadData();
		}
 
		void ReloadData()
		{
			if (_isDisposed)
				return;
 
			CollectionView.ReloadData();
			CollectionView.CollectionViewLayout.InvalidateLayout();
		}
 
		public class ShellSectionHeaderCell : UICollectionViewCell
		{
			public UIColor SelectedColor { get; set; }
			public UIColor UnSelectedColor { get; set; }
 
			[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
			public ShellSectionHeaderCell()
			{
 
			}
 
			[Export("initWithFrame:")]
			[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
			public ShellSectionHeaderCell(CGRect frame) : base(frame)
			{
				Label = new UILabel();
				Label.TextAlignment = UITextAlignment.Center;
				Label.Font = UIFont.BoldSystemFontOfSize(14);
				ContentView.AddSubview(Label);
			}
 
			public override bool Selected
			{
				get => base.Selected;
				set
				{
					base.Selected = value;
					Label.TextColor = value ? SelectedColor : UnSelectedColor;
				}
			}
 
			public UILabel Label { get; }
 
			public override void LayoutSubviews()
			{
				base.LayoutSubviews();
 
				Label.Frame = Bounds;
			}
 
			public override CGSize SizeThatFits(CGSize size)
			{
				return new CGSize(Label.SizeThatFits(size).Width + 30, 35);
			}
		}
	}
}