File: Compatibility\Handlers\ListView\iOS\ViewCellRenderer.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.ComponentModel;
using Microsoft.Maui.Controls.Compatibility;
using Microsoft.Maui.Controls.Internals;
using ObjCRuntime;
using UIKit;
using RectangleF = CoreGraphics.CGRect;
using SizeF = CoreGraphics.CGSize;
 
namespace Microsoft.Maui.Controls.Handlers.Compatibility
{
	public class ViewCellRenderer : CellRenderer
	{
		[Preserve(Conditional = true)]
		public ViewCellRenderer()
		{
		}
 
		public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
		{
			Performance.Start(out string reference);
 
			var viewCell = (ViewCell)item;
 
			var cell = reusableCell as ViewTableCell;
			if (cell == null)
				cell = new ViewTableCell(item.GetType().FullName);
 
			cell.ViewCell = viewCell;
 
			SetRealCell(item, cell);
 
			UpdateBackground(cell, item);
 
			SetAccessibility(cell, item);
 
			Performance.Stop(reference);
			return cell;
		}
 
		internal sealed class ViewTableCell : UITableViewCell, INativeElementView
		{
			IMauiContext MauiContext => ViewCell?.FindMauiContext();
			WeakReference<IPlatformViewHandler> _rendererRef;
			WeakReference<ViewCell> _viewCell;
 
			Element INativeElementView.Element => ViewCell;
			internal bool SupressSeparator { get; set; }
			bool _disposed;
 
			public ViewTableCell(string key) : base(UITableViewCellStyle.Default, key)
			{
			}
 
			public ViewCell ViewCell
			{
				get => _viewCell?.GetTargetOrDefault();
				set
				{
					if (_viewCell is not null)
					{
						if (_viewCell.TryGetTarget(out var viewCell) && viewCell == value)
							return;
						viewCell?.Handler?.DisconnectHandler();
					}
					UpdateCell(value);
				}
			}
 
			void UpdateIsEnabled(bool isEnabled)
			{
				UserInteractionEnabled = isEnabled;
#pragma warning disable CA1416, CA1422 // TODO: TextLabel is unsupported on: 'ios' 14.0 and later
				TextLabel.Enabled = isEnabled;
#pragma warning restore C1416, CA1422
			}
 
			void ViewCellPropertyChanged(object sender, PropertyChangedEventArgs e)
			{
				var viewCell = (ViewCell)sender;
				var realCell = (ViewTableCell)GetRealCell(viewCell);
 
				if (e.PropertyName == Cell.IsEnabledProperty.PropertyName)
					UpdateIsEnabled(viewCell.IsEnabled);
			}
 
			public override void LayoutSubviews()
			{
				Performance.Start(out string reference);
 
				//This sets the content views frame.
				base.LayoutSubviews();
 
				//TODO: Determine how best to hide the separator line when there is an accessory on the cell
				if (SupressSeparator && Accessory == UITableViewCellAccessory.None)
				{
					var oldFrame = Frame;
					ContentView.Bounds = new RectangleF(oldFrame.Location, new SizeF(oldFrame.Width, oldFrame.Height + 0.5f));
				}
 
				if (_rendererRef == null)
					return;
 
				if (_rendererRef.TryGetTarget(out IPlatformViewHandler handler))
				{
					var contentFrame = ContentView.Frame;
					handler.LayoutVirtualView(new RectangleF(0, 0, contentFrame.Width, contentFrame.Height));
				}
 
				Performance.Stop(reference);
			}
 
			public override SizeF SizeThatFits(SizeF size)
			{
				Performance.Start(out string reference);
 
				if (!_rendererRef.TryGetTarget(out IPlatformViewHandler handler))
					return base.SizeThatFits(size);
 
				if (handler.VirtualView == null)
					return SizeF.Empty;
 
				double width = size.Width;
				var height = size.Height > 0 ? size.Height : double.PositiveInfinity;
				var result = handler.MeasureVirtualView(new SizeF(width, height));
				if (result == null)
					return base.SizeThatFits(size);
 
				// make sure to add in the separator if needed
				var finalheight = (float)result.Value.Height + (SupressSeparator ? 0f : 1f) / UIScreen.MainScreen.Scale;
 
				Performance.Stop(reference);
 
				return new SizeF(size.Width, finalheight);
			}
 
			protected override void Dispose(bool disposing)
			{
				if (_disposed)
					return;
 
				if (disposing)
				{
					IPlatformViewHandler renderer;
					if (_rendererRef != null && _rendererRef.TryGetTarget(out renderer) && renderer.VirtualView != null)
					{
						renderer.VirtualView.DisposeModalAndChildHandlers();
						_rendererRef = null;
					}
 
					if (ViewCell is ViewCell viewCell)
					{
						viewCell.PropertyChanged -= ViewCellPropertyChanged;
						viewCell.View.MeasureInvalidated -= OnMeasureInvalidated;
						SetRealCell(viewCell, null);
					}
					_viewCell = null;
				}
 
				_disposed = true;
 
				base.Dispose(disposing);
			}
 
			IPlatformViewHandler GetNewRenderer()
			{
				if (ViewCell is not ViewCell viewCell || viewCell.View == null)
					throw new InvalidOperationException($"ViewCell must have a {nameof(viewCell.View)}");
 
				var newRenderer = viewCell.View.ToHandler(viewCell.View.FindMauiContext());
				_rendererRef = new WeakReference<IPlatformViewHandler>(newRenderer);
				ContentView.ClearSubviews();
				ContentView.AddSubview(newRenderer.VirtualView.ToPlatform());
				return (IPlatformViewHandler)newRenderer;
			}
 
			void UpdateCell(ViewCell cell)
			{
				Performance.Start(out string reference);
 
				if (ViewCell is ViewCell oldCell)
				{
					BeginInvokeOnMainThread(oldCell.SendDisappearing);
					oldCell.PropertyChanged -= ViewCellPropertyChanged;
					oldCell.View.MeasureInvalidated -= OnMeasureInvalidated;
				}
 
				if (cell is null)
				{
					_viewCell = null;
					_rendererRef = null;
					ContentView.ClearSubviews();
					return;
				}
 
				_viewCell = new(cell);
 
				cell.PropertyChanged += ViewCellPropertyChanged;
				BeginInvokeOnMainThread(cell.SendAppearing);
 
				IPlatformViewHandler renderer;
				if (_rendererRef == null || !_rendererRef.TryGetTarget(out renderer))
					renderer = GetNewRenderer();
				else
				{
					var viewHandlerType = MauiContext.Handlers.GetHandlerType(cell.View.GetType());
					var reflectableType = renderer as System.Reflection.IReflectableType;
					var rendererType = reflectableType != null ? reflectableType.GetTypeInfo().AsType() : (renderer != null ? renderer.GetType() : typeof(System.Object));
 
					if (rendererType == viewHandlerType/* || (renderer is Platform.DefaultRenderer && type == null)*/)
						renderer.SetVirtualView(cell.View);
					else
					{
						//when cells are getting reused the element could be already set to another cell
						//so we should dispose based on the renderer and not the renderer.Element
						renderer.DisposeHandlersAndChildren();
						renderer = GetNewRenderer();
					}
				}
 
				UpdateIsEnabled(cell.IsEnabled);
				cell.View.MeasureInvalidated += OnMeasureInvalidated;
				Performance.Stop(reference);
			}
 
			void OnMeasureInvalidated(object sender, EventArgs e)
			{
				SetNeedsLayout();
			}
 
 
		}
	}
}