File: Compatibility\Handlers\Shell\iOS\ShellSearchResultsRenderer.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections.Specialized;
using Foundation;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Internals;
using ObjCRuntime;
using UIKit;
 
namespace Microsoft.Maui.Controls.Platform.Compatibility
{
	public class ShellSearchResultsRenderer : UITableViewController, IShellSearchResultsRenderer
	{
		#region IShellSearchResultsRenderer
 
		SearchHandler IShellSearchResultsRenderer.SearchHandler
		{
			get { return SearchHandler; }
			set
			{
				SearchHandler = value;
				OnSearchHandlerSet();
			}
		}
 
		UIViewController IShellSearchResultsRenderer.ViewController => this;
 
		#endregion IShellSearchResultsRenderer
 
		readonly IShellContext _context;
		DataTemplate _defaultTemplate;
 
		// If data templates were horses, this is a donkey
		DataTemplate DefaultTemplate
		{
			get
			{
				return _defaultTemplate ?? (_defaultTemplate = new DataTemplate(() =>
					{
						var label = new Label();
 
						if (RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported)
						{
#pragma warning disable CS0618
#if NET8_0
#pragma warning disable IL2026 // FeatureGuardAttribute is not supported on .NET 8
#endif
							label.SetBinding(Label.TextProperty, SearchHandler.DisplayMemberName ?? ".");
#if NET8_0
#pragma warning restore IL2026 // FeatureGuardAttribute is not supported on .NET 8
#endif
#pragma warning restore CS0618
						}
						else
						{
#pragma warning disable CS0618
							if (SearchHandler.DisplayMemberName is not null)
							{
								Application.Current?.FindMauiContext()?.CreateLogger<ShellSearchResultsRenderer>()?.LogError(TrimmerConstants.SearchHandlerDisplayMemberNameNotSupportedWarning);
								throw new InvalidOperationException(TrimmerConstants.SearchHandlerDisplayMemberNameNotSupportedWarning);
							}
#pragma warning restore CS0618
 
							label.SetBinding(Label.TextProperty, static (object o) => o);
						}
 
						label.HorizontalTextAlignment = TextAlignment.Center;
						label.VerticalTextAlignment = TextAlignment.Center;
 
						return label;
					}));
			}
		}
 
		public event EventHandler<object> ItemSelected;
 
		public ShellSearchResultsRenderer(IShellContext context)
		{
			_context = context;
		}
 
		protected UITableViewRowAnimation DeleteRowsAnimation { get; set; } = UITableViewRowAnimation.Automatic;
		protected UITableViewRowAnimation InsertRowsAnimation { get; set; } = UITableViewRowAnimation.Automatic;
		protected UITableViewRowAnimation ReloadRowsAnimation { get; set; } = UITableViewRowAnimation.Automatic;
		protected UITableViewRowAnimation ReloadSectionsAnimation { get; set; } = UITableViewRowAnimation.Automatic;
		private ISearchHandlerController SearchController => SearchHandler;
		private SearchHandler SearchHandler { get; set; }
 
		protected override void Dispose(bool disposing)
		{
			base.Dispose(disposing);
 
			if (disposing)
			{
				if (SearchHandler != null)
				{
					var listProxy = (INotifyCollectionChanged)SearchController.ListProxy;
					if (listProxy != null)
						listProxy.CollectionChanged -= OnProxyCollectionChanged;
					SearchController.ListProxyChanged -= OnListProxyChanged;
				}
 
				SearchHandler = null;
			}
 
		}
 
		public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
		{
			var proxy = SearchController.ListProxy;
			int row = indexPath.Row;
			var context = proxy[row];
 
			var template = SearchHandler.ItemTemplate;
 
			if (template == null)
			{
				template = DefaultTemplate;
			}
 
			var cellId = ((IDataTemplateController)template.SelectDataTemplate(context, _context.Shell)).IdString;
 
			var cell = (UIContainerCell)tableView.DequeueReusableCell(cellId);
 
			if (cell == null)
			{
				var view = (View)template.CreateContent(context, _context.Shell);
				view.BindingContext = context;
				view.Parent = _context.Shell;
				cell = new UIContainerCell(cellId, view);
			}
			else
			{
				cell.View.BindingContext = context;
			}
 
			return cell;
		}
 
		public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
		{
			var item = SearchController.ListProxy[indexPath.Row];
			ItemSelected?.Invoke(this, item);
		}
 
		public override nint NumberOfSections(UITableView tableView)
		{
			return 1;
		}
 
		public override nint RowsInSection(UITableView tableView, nint section)
		{
			if (SearchController.ListProxy == null)
				return 0;
			return SearchController.ListProxy.Count;
		}
 
		NSIndexPath[] GetPaths(int section, int index, int count)
		{
			var paths = new NSIndexPath[count];
			for (var i = 0; i < paths.Length; i++)
				paths[i] = NSIndexPath.FromRowSection(index + i, section);
 
			return paths;
		}
 
		void OnListProxyChanged(object sender, ListProxyChangedEventArgs e)
		{
			if (e.OldList != null)
			{
				((INotifyCollectionChanged)e.OldList).CollectionChanged -= OnProxyCollectionChanged;
			}
			// Full reset
			TableView.ReloadData();
 
			if (e.NewList != null)
			{
				((INotifyCollectionChanged)e.NewList).CollectionChanged += OnProxyCollectionChanged;
			}
		}
 
		void OnProxyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			int section = 0;
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
 
					if (e.NewStartingIndex == -1)
						goto case NotifyCollectionChangedAction.Reset;
 
					TableView.BeginUpdates();
					TableView.InsertRows(GetPaths(section, e.NewStartingIndex, e.NewItems.Count), InsertRowsAnimation);
					TableView.EndUpdates();
 
					break;
 
				case NotifyCollectionChangedAction.Remove:
					if (e.OldStartingIndex == -1)
						goto case NotifyCollectionChangedAction.Reset;
					TableView.BeginUpdates();
					TableView.DeleteRows(GetPaths(section, e.OldStartingIndex, e.OldItems.Count), DeleteRowsAnimation);
 
					TableView.EndUpdates();
					break;
 
				case NotifyCollectionChangedAction.Move:
					if (e.OldStartingIndex == -1 || e.NewStartingIndex == -1)
						goto case NotifyCollectionChangedAction.Reset;
					TableView.BeginUpdates();
					for (var i = 0; i < e.OldItems.Count; i++)
					{
						var oldIndex = e.OldStartingIndex;
						var newIndex = e.NewStartingIndex;
 
						if (e.NewStartingIndex < e.OldStartingIndex)
						{
							oldIndex += i;
							newIndex += i;
						}
 
						TableView.MoveRow(NSIndexPath.FromRowSection(oldIndex, section), NSIndexPath.FromRowSection(newIndex, section));
					}
					TableView.EndUpdates();
					break;
 
				case NotifyCollectionChangedAction.Replace:
					if (e.OldStartingIndex == -1)
						goto case NotifyCollectionChangedAction.Reset;
					TableView.BeginUpdates();
					TableView.ReloadRows(GetPaths(section, e.OldStartingIndex, e.OldItems.Count), ReloadRowsAnimation);
					TableView.EndUpdates();
					break;
 
				case NotifyCollectionChangedAction.Reset:
					TableView.ReloadData();
					return;
			}
		}
 
		void OnSearchHandlerSet()
		{
			SearchController.ListProxyChanged += OnListProxyChanged;
		}
	}
}