File: Platform\GestureManager\GesturePlatformManager.iOS.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;
using System.Runtime.Versioning;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform.iOS;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Graphics;
using ObjCRuntime;
using UIKit;
using PlatformView = UIKit.UIView;
 
namespace Microsoft.Maui.Controls.Platform
{
	class GesturePlatformManager : IDisposable
	{
		readonly NotifyCollectionChangedEventHandler _collectionChangedHandler;
 
		readonly Dictionary<IGestureRecognizer, List<UIGestureRecognizer?>> _gestureRecognizers = new Dictionary<IGestureRecognizer, List<UIGestureRecognizer?>>();
		readonly List<INativeObject> _interactions = new List<INativeObject>();
		readonly IPlatformViewHandler _handler;
 
		bool _disposed;
		WeakReference<PlatformView>? _platformView;
		UIAccessibilityTrait _addedFlags;
		bool? _defaultAccessibilityRespondsToUserInteraction;
 
		double _previousScale = 1.0;
		ShouldReceiveTouchProxy? _proxy;
		DragAndDropDelegate? _dragAndDropDelegate;
 
		public GesturePlatformManager(IViewHandler handler)
		{
			if (handler == null)
				throw new ArgumentNullException(nameof(handler));
 
			_handler = (IPlatformViewHandler)handler;
 
			if (_handler?.ToPlatform() is not PlatformView target)
				throw new ArgumentNullException(nameof(handler.PlatformView));
 
			_platformView = new WeakReference<PlatformView>(target);
 
			_collectionChangedHandler = GestureRecognizersOnCollectionChanged;
 
			// In XF this was called inside ViewDidLoad
			if (_handler.VirtualView is View view)
				OnElementChanged(this, new VisualElementChangedEventArgs(null, view));
			else
				throw new ArgumentNullException(nameof(handler.VirtualView));
		}
 
		protected virtual PlatformView? PlatformView
		{
			get
			{
				if (_platformView?.TryGetTarget(out var target) == true)
					return target;
				return null;
			}
		}
 
		ObservableCollection<IGestureRecognizer>? ElementGestureRecognizers =>
			(_handler.VirtualView as Element)?.GetCompositeGestureRecognizers() as ObservableCollection<IGestureRecognizer>;
 
		internal void Disconnect()
		{
			if (ElementGestureRecognizers != null)
				ElementGestureRecognizers.CollectionChanged -= _collectionChangedHandler;
		}
 
		public void Dispose()
		{
			if (_disposed)
				return;
 
			_disposed = true;
 
			foreach (var kvp in _gestureRecognizers)
			{
				if (TryGetTapGestureRecognizer(kvp.Key, out TapGestureRecognizer? tapGestureRecognizer) &&
					tapGestureRecognizer != null)
				{
					tapGestureRecognizer.PropertyChanged -= OnTapGestureRecognizerPropertyChanged;
				}
 
				foreach (var uiGestureRecognizer in kvp.Value)
				{
					if (uiGestureRecognizer is null)
						continue;
 
					if (PlatformView != null)
						PlatformView.RemoveGestureRecognizer(uiGestureRecognizer);
					uiGestureRecognizer.ShouldReceiveTouch = null;
					uiGestureRecognizer.Dispose();
				}
			}
 
			if (PlatformView != null && OperatingSystem.IsIOSVersionAtLeast(11))
			{
				foreach (IUIInteraction interaction in _interactions)
				{
					PlatformView.RemoveInteraction(interaction);
				}
			}
 
			_interactions.Clear();
			_gestureRecognizers.Clear();
 
			_dragAndDropDelegate?.Disconnect();
			_dragAndDropDelegate = null;
 
			Disconnect();
 
			_platformView = null;
		}
 
		static IList<GestureElement>? GetChildGestures(
			CGPoint originPoint,
			WeakReference weakEventTracker, WeakReference weakRecognizer, GesturePlatformManager? eventTracker, View? view)
		{
			if (!weakRecognizer.IsAlive)
				return null;
 
			if (eventTracker == null || eventTracker._disposed || view == null)
				return null;
 
			var childGestures = view.GetChildElements(new Point(originPoint.X, originPoint.Y));
			return childGestures;
		}
 
		static void ProcessRecognizerHandlerTap(
			WeakReference weakEventTracker,
			WeakReference weakRecognizer,
			CGPoint originPoint,
			int uiTapGestureRecognizerNumberOfTapsRequired,
			UITapGestureRecognizer? uITapGestureRecognizer = null)
		{
			var recognizer = weakRecognizer.Target as IGestureRecognizer;
			var eventTracker = weakEventTracker.Target as GesturePlatformManager;
			var view = eventTracker?._handler?.VirtualView as View;
 
			WeakReference? weakPlatformRecognizer = null;
			if (uITapGestureRecognizer != null)
				weakPlatformRecognizer = new WeakReference(uITapGestureRecognizer);
 
			if (recognizer is TapGestureRecognizer tapGestureRecognizer)
			{
				var childGestures = GetChildGestures(originPoint, weakEventTracker, weakRecognizer, eventTracker, view);
 
				if (childGestures?.HasChildGesturesFor<TapGestureRecognizer>(x => x.NumberOfTapsRequired == uiTapGestureRecognizerNumberOfTapsRequired) == true)
					return;
 
				if (view != null)
					tapGestureRecognizer.SendTapped(view, (relativeTo) => CalculatePosition(relativeTo, originPoint, weakPlatformRecognizer, weakEventTracker));
			}
			else if (recognizer is ChildGestureRecognizer childGestureRecognizer)
			{
				var childGestures = GetChildGestures(originPoint, weakEventTracker, weakRecognizer, eventTracker, view);
 
				var recognizers = childGestures?.GetChildGesturesFor<TapGestureRecognizer>(x => x.NumberOfTapsRequired == uiTapGestureRecognizerNumberOfTapsRequired);
 
				if (recognizers == null || weakRecognizer.Target == null)
					return;
 
				var childTapGestureRecognizer = childGestureRecognizer.GestureRecognizer as TapGestureRecognizer;
				foreach (var item in recognizers)
					if (item == childTapGestureRecognizer && view != null)
						childTapGestureRecognizer.SendTapped(view, (relativeTo) => CalculatePosition(relativeTo, originPoint, weakPlatformRecognizer, weakEventTracker));
			}
		}
 
 
		static Point? CalculatePosition(IElement? element, CGPoint originPoint, WeakReference? weakPlatformRecognizer, WeakReference weakEventTracker)
		{
			var eventTracker = weakEventTracker.Target as GesturePlatformManager;
			var virtualView = eventTracker?._handler?.VirtualView as View;
			var platformRecognizer = weakPlatformRecognizer?.Target as UIGestureRecognizer;
			var platformView = element?.ToPlatform();
 
			if (virtualView == null)
				return null;
 
			if (platformRecognizer == null)
			{
				if (virtualView == element)
					return new Point((int)originPoint.X, (int)originPoint.Y);
 
				var targetViewScreenLocation = virtualView.GetLocationOnScreen();
 
				if (!targetViewScreenLocation.HasValue)
					return null;
 
				var windowX = targetViewScreenLocation.Value.X + originPoint.X;
				var windowY = targetViewScreenLocation.Value.Y + originPoint.Y;
 
				if (element == null)
					return new Point(windowX, windowY);
 
				if (platformView is PlatformView uiView)
				{
					var location = uiView.GetLocationOnScreen();
 
					var x = windowX - location.X;
					var y = windowY - location.Y;
 
					return new Point(x, y);
				}
 
				return null;
			}
 
			CGPoint? result = null;
			if (element == null)
				result = platformRecognizer.LocationInView(null);
			else if (platformView is PlatformView view)
				result = platformRecognizer.LocationInView(view);
 
			if (result == null)
				return null;
 
			return new Point((int)result.Value.X, (int)result.Value.Y);
 
		}
 
		protected virtual List<UIGestureRecognizer?>? GetPlatformRecognizer(IGestureRecognizer recognizer)
		{
			if (recognizer == null)
				return null;
 
			var weakRecognizer = new WeakReference(recognizer);
			var weakEventTracker = new WeakReference(this);
 
			var tapGestureRecognizer = CreateTapRecognizer(weakEventTracker, weakRecognizer);
 
			if (tapGestureRecognizer != null)
			{
				return new List<UIGestureRecognizer?> { tapGestureRecognizer };
			}
 
			var pointerGestureRecognizer = recognizer as PointerGestureRecognizer;
 
			if (pointerGestureRecognizer != null && OperatingSystem.IsIOSVersionAtLeast(13))
			{
				var uiRecognizers = CreatePointerRecognizer(weakRecognizer, weakEventTracker);
				return uiRecognizers;
			}
 
			var swipeRecognizer = recognizer as SwipeGestureRecognizer;
			if (swipeRecognizer != null)
			{
				var returnAction = new Action<SwipeDirection>((direction) =>
				{
					var swipeGestureRecognizer = weakRecognizer.Target as SwipeGestureRecognizer;
					var eventTracker = weakEventTracker.Target as GesturePlatformManager;
					var view = eventTracker?._handler.VirtualView as View;
 
					if (swipeGestureRecognizer != null && view != null)
						swipeGestureRecognizer.SendSwiped(view, direction);
				});
				var uiRecognizer = CreateSwipeRecognizer(swipeRecognizer.Direction, returnAction, 1);
				return new List<UIGestureRecognizer?> { uiRecognizer };
			}
 
			var pinchRecognizer = recognizer as IPinchGestureController;
			if (pinchRecognizer != null)
			{
				double startingScale = 1;
				var uiRecognizer = CreatePinchRecognizer(r =>
				{
					if (weakRecognizer.Target is IPinchGestureController pinchGestureRecognizer &&
						weakEventTracker.Target is GesturePlatformManager eventTracker &&
						eventTracker._handler?.VirtualView is View view &&
						eventTracker.PlatformView is { } platformView)
					{
						var oldScale = eventTracker._previousScale;
						var originPoint = r.LocationInView(null);
						originPoint = platformView.Window.ConvertPointToView(originPoint, platformView);
 
						var scaledPoint = new Point(originPoint.X / view.Width, originPoint.Y / view.Height);
 
						switch (r.State)
						{
							case UIGestureRecognizerState.Began:
								if (r.NumberOfTouches < 2)
									return;
 
								pinchGestureRecognizer.SendPinchStarted(view, scaledPoint);
								startingScale = view.Scale;
								break;
							case UIGestureRecognizerState.Changed:
								if (r.NumberOfTouches < 2 && pinchGestureRecognizer.IsPinching)
								{
									r.State = UIGestureRecognizerState.Ended;
									pinchGestureRecognizer.SendPinchEnded(view);
									return;
								}
								var scale = r.Scale;
								var delta = 1.0;
								var dif = Math.Abs(scale - oldScale) * startingScale;
								if (oldScale < scale)
									delta = 1 + dif;
								if (oldScale > scale)
									delta = 1 - dif;
 
								pinchGestureRecognizer.SendPinch(view, delta, scaledPoint);
								eventTracker._previousScale = scale;
								break;
							case UIGestureRecognizerState.Cancelled:
							case UIGestureRecognizerState.Failed:
								if (pinchGestureRecognizer.IsPinching)
									pinchGestureRecognizer.SendPinchCanceled(view);
								break;
							case UIGestureRecognizerState.Ended:
								if (pinchGestureRecognizer.IsPinching)
									pinchGestureRecognizer.SendPinchEnded(view);
								eventTracker._previousScale = 1;
								break;
						}
					}
				});
				return new List<UIGestureRecognizer?> { uiRecognizer };
			}
 
			var panRecognizer = recognizer as PanGestureRecognizer;
			if (panRecognizer != null)
			{
				var uiRecognizer = CreatePanRecognizer(panRecognizer.TouchPoints, r =>
				{
					var eventTracker = weakEventTracker.Target as GesturePlatformManager;
					var view = eventTracker?._handler?.VirtualView as View;
 
					var panGestureRecognizer = weakRecognizer.Target as IPanGestureController;
					if (panGestureRecognizer != null && view != null)
					{
						switch (r.State)
						{
							case UIGestureRecognizerState.Began:
								if (r.NumberOfTouches != ((PanGestureRecognizer)panGestureRecognizer).TouchPoints)
									return;
								panGestureRecognizer.SendPanStarted(view, PanGestureRecognizer.CurrentId.Value);
								break;
							case UIGestureRecognizerState.Changed:
								if (r.NumberOfTouches != ((PanGestureRecognizer)panGestureRecognizer).TouchPoints)
								{
									r.State = UIGestureRecognizerState.Ended;
									panGestureRecognizer.SendPanCompleted(view, PanGestureRecognizer.CurrentId.Value);
									PanGestureRecognizer.CurrentId.Increment();
									return;
								}
								var translationInView = r.TranslationInView(eventTracker?.PlatformView);
								panGestureRecognizer.SendPan(view, translationInView.X, translationInView.Y, PanGestureRecognizer.CurrentId.Value);
								break;
							case UIGestureRecognizerState.Cancelled:
							case UIGestureRecognizerState.Failed:
								panGestureRecognizer.SendPanCanceled(view, PanGestureRecognizer.CurrentId.Value);
								PanGestureRecognizer.CurrentId.Increment();
								break;
							case UIGestureRecognizerState.Ended:
								if (r.NumberOfTouches != ((PanGestureRecognizer)panGestureRecognizer).TouchPoints)
								{
									panGestureRecognizer.SendPanCompleted(view, PanGestureRecognizer.CurrentId.Value);
									PanGestureRecognizer.CurrentId.Increment();
								}
								break;
						}
					}
				});
				return new List<UIGestureRecognizer?> { uiRecognizer };
			}
 
			return null;
		}
 
		UIPanGestureRecognizer CreatePanRecognizer(int numTouches, Action<UIPanGestureRecognizer> action)
		{
			var result = new UIPanGestureRecognizer(action);
			result.MinimumNumberOfTouches = result.MaximumNumberOfTouches = (uint)numTouches;
 
			// enable touches to pass through so that underlying scrolling views will still receive the pan
			result.ShouldRecognizeSimultaneously = (g, o) => Application.Current?.OnThisPlatform().GetPanGestureRecognizerShouldRecognizeSimultaneously() ?? false;
			return result;
		}
 
		UIPinchGestureRecognizer CreatePinchRecognizer(Action<UIPinchGestureRecognizer> action)
		{
			var result = new UIPinchGestureRecognizer(action);
			return result;
		}
 
		UISwipeGestureRecognizer CreateSwipeRecognizer(SwipeDirection direction, Action<SwipeDirection> action, int numFingers = 1)
		{
			var result = new UISwipeGestureRecognizer();
			result.NumberOfTouchesRequired = (uint)numFingers;
			result.Direction = (UISwipeGestureRecognizerDirection)direction;
			result.ShouldRecognizeSimultaneously = (g, o) => true;
			result.AddTarget(() => action(direction));
			return result;
		}
 
		[SupportedOSPlatform("ios13.0")]
		[SupportedOSPlatform("maccatalyst13.0")]
		List<UIGestureRecognizer?> CreatePointerRecognizer(WeakReference weakRecognizer, WeakReference weakEventTracker)
		{
			bool exited = false;
 
			Action<UIGestureRecognizer> action = (pointerGesture) =>
			{
				if (weakRecognizer.Target is PointerGestureRecognizer pointerGestureRecognizer &&
					weakEventTracker.Target is GesturePlatformManager eventTracker &&
					eventTracker._handler?.VirtualView is View view)
				{
					var originPoint = pointerGesture.LocationInView(eventTracker?.PlatformView);
					var platformPointerArgs = new PlatformPointerEventArgs(pointerGesture.View, pointerGesture);
 
					switch (pointerGesture.State)
					{
						case UIGestureRecognizerState.Began:
							exited = false;
							if (pointerGesture is UIHoverGestureRecognizer)
								pointerGestureRecognizer.SendPointerEntered(view, (relativeTo) => CalculatePosition(relativeTo, originPoint, weakRecognizer, weakEventTracker), platformPointerArgs);
							else
								pointerGestureRecognizer.SendPointerPressed(view, (relativeTo) => CalculatePosition(relativeTo, originPoint, weakRecognizer, weakEventTracker), platformPointerArgs);
							break;
						case UIGestureRecognizerState.Changed:
							if (exited)
								break;
 
							if (pointerGesture is UIHoverGestureRecognizer)
								pointerGestureRecognizer.SendPointerMoved(view, (relativeTo) => CalculatePosition(relativeTo, originPoint, weakRecognizer, weakEventTracker), platformPointerArgs);
							else
							{
								var bounds = eventTracker?.PlatformView?.Bounds;
								if (bounds is not null && bounds.Value.Contains(originPoint))
									pointerGestureRecognizer.SendPointerMoved(view, (relativeTo) => CalculatePosition(relativeTo, originPoint, weakRecognizer, weakEventTracker), platformPointerArgs);
								else
								{
									pointerGestureRecognizer.SendPointerExited(view, (relativeTo) => CalculatePosition(relativeTo, originPoint, weakRecognizer, weakEventTracker), platformPointerArgs);
									exited = true;
									pointerGesture.State = UIGestureRecognizerState.Ended;
									break;
								}
							}
							break;
						case UIGestureRecognizerState.Cancelled:
						case UIGestureRecognizerState.Failed:
						case UIGestureRecognizerState.Ended:
							if (exited)
								break;
 
							if (pointerGesture is UIHoverGestureRecognizer)
								pointerGestureRecognizer.SendPointerExited(view, (relativeTo) => CalculatePosition(relativeTo, originPoint, weakRecognizer, weakEventTracker), platformPointerArgs);
							else
								pointerGestureRecognizer.SendPointerReleased(view, (relativeTo) => CalculatePosition(relativeTo, originPoint, weakRecognizer, weakEventTracker), platformPointerArgs);
							break;
					}
				}
			};
 
			var result = new List<UIGestureRecognizer?>()
			{
				new UIHoverGestureRecognizer((gesture) => action.Invoke(gesture)) { ShouldRecognizeSimultaneously = (g, o) => true },
				new CustomPressGestureRecognizer((gesture) => action.Invoke(gesture)) { ShouldRecognizeSimultaneously = (g, o) => true }
 
			};
			return result;
		}
 
		UITapGestureRecognizer? CreateTapRecognizer(
			WeakReference weakEventTracker,
			WeakReference weakRecognizer)
		{
			if (!TryGetTapGestureRecognizer(weakRecognizer.Target as IGestureRecognizer, out TapGestureRecognizer? tapGesture))
				return null;
 
			if (tapGesture == null)
				return null;
 
			Action<UITapGestureRecognizer> action = new Action<UITapGestureRecognizer>((sender) =>
			{
				var eventTracker = weakEventTracker.Target as GesturePlatformManager;
				var originPoint = sender.LocationInView(eventTracker?.PlatformView);
				ProcessRecognizerHandlerTap(weakEventTracker, weakRecognizer, originPoint, (int)sender.NumberOfTapsRequired, sender);
			});
 
			var result = new UITapGestureRecognizer(action)
			{
				NumberOfTapsRequired = (uint)tapGesture.NumberOfTapsRequired,
				ShouldRecognizeSimultaneously = ShouldRecognizeTapsTogether
			};
 
			// For whatever reason the secondary mask doesn't work on catalyst
			// it only works when you have a mouse connected to an iPad
			// so we just ignore setting the mask if the user is running catalyst
			// right click is handled by adding a UIContextMenu interaction
			if (OperatingSystem.IsIOSVersionAtLeast(13, 4) && !OperatingSystem.IsMacCatalyst())
			{
				UIEventButtonMask uIEventButtonMask = (UIEventButtonMask)0;
 
				if ((tapGesture.Buttons & ButtonsMask.Primary) == ButtonsMask.Primary)
					uIEventButtonMask |= UIEventButtonMask.Primary;
 
				if ((tapGesture.Buttons & ButtonsMask.Secondary) == ButtonsMask.Secondary)
					uIEventButtonMask |= UIEventButtonMask.Secondary;
 
				result.ButtonMaskRequired = uIEventButtonMask;
			}
 
			return result;
		}
 
		static bool ShouldRecognizeTapsTogether(UIGestureRecognizer gesture, UIGestureRecognizer other)
		{
			// If multiple tap gestures are potentially firing (because multiple tap gesture recognizers have been
			// added to the MAUI Element), we want to allow them to fire simultaneously if they have the same number
			// of taps and touches
 
			var tap = gesture as UITapGestureRecognizer;
			if (tap == null)
			{
				return false;
			}
 
			var otherTap = other as UITapGestureRecognizer;
			if (otherTap == null)
			{
				return false;
			}
 
			if (!Equals(tap.View, otherTap.View))
			{
				return false;
			}
 
			if (tap.NumberOfTapsRequired != otherTap.NumberOfTapsRequired)
			{
				return false;
			}
 
			if (tap.NumberOfTouchesRequired != otherTap.NumberOfTouchesRequired)
			{
				return false;
			}
 
			return true;
		}
 
		bool TryGetTapGestureRecognizer(IGestureRecognizer? recognizer, out TapGestureRecognizer? tapGestureRecognizer)
		{
			tapGestureRecognizer =
					recognizer as TapGestureRecognizer ??
					(recognizer as ChildGestureRecognizer)?.GestureRecognizer as TapGestureRecognizer;
 
			return tapGestureRecognizer != null;
		}
 
		void LoadRecognizers()
		{
			if (ElementGestureRecognizers == null)
				return;
 
			UIDragInteraction? uIDragInteraction = null;
			UIDropInteraction? uIDropInteraction = null;
 
			if (_dragAndDropDelegate != null && PlatformView != null)
			{
				if (OperatingSystem.IsIOSVersionAtLeast(11))
				{
					foreach (var interaction in PlatformView.Interactions)
					{
						if (interaction is UIDragInteraction uIDrag && uIDrag.Delegate == _dragAndDropDelegate)
							uIDragInteraction = uIDrag;
 
						if (interaction is UIDropInteraction uiDrop && uiDrop.Delegate == _dragAndDropDelegate)
							uIDropInteraction = uiDrop;
					}
				}
			}
 
			bool dragFound = false;
			bool dropFound = false;
 
			if (PlatformView != null &&
				_handler.VirtualView is View v &&
				v.TapGestureRecognizerNeedsDelegate() &&
				(PlatformView.AccessibilityTraits & UIAccessibilityTrait.Button) != UIAccessibilityTrait.Button)
			{
				PlatformView.AccessibilityTraits |= UIAccessibilityTrait.Button;
				_addedFlags |= UIAccessibilityTrait.Button;
				if (OperatingSystem.IsIOSVersionAtLeast(13) || OperatingSystem.IsMacCatalystVersionAtLeast(13)
#if TVOS
				|| OperatingSystem.IsTvOSVersionAtLeast(11)
#endif
					)
				{
					_defaultAccessibilityRespondsToUserInteraction = PlatformView.AccessibilityRespondsToUserInteraction;
					PlatformView.AccessibilityRespondsToUserInteraction = true;
				}
			}
 
			_interactions.Clear();
 
			for (int i = 0; i < ElementGestureRecognizers.Count; i++)
			{
				IGestureRecognizer recognizer = ElementGestureRecognizers[i];
 
				if (_gestureRecognizers.ContainsKey(recognizer))
					continue;
 
				if (TryGetTapGestureRecognizer(recognizer, out TapGestureRecognizer? tapGestureRecognizer) &&
					tapGestureRecognizer != null)
				{
					tapGestureRecognizer.PropertyChanged += OnTapGestureRecognizerPropertyChanged;
				}
 
				// AddFakeRightClickForMacCatalyst returns the button mask for the processed tap gesture
				// If a fake gesture wasn't added then it just returns 0
				// If a fake gesture was added then we return the button mask fromm the tap gesture
				// If the user only cares about right click then we'll just exit now
				// so that an additional tap gesture doesn't get added
				if (AddFakeRightClickForMacCatalyst(recognizer) == ButtonsMask.Secondary)
				{
					continue;
				}
 
				if (OperatingSystem.IsIOSVersionAtLeast(11) && recognizer is DragGestureRecognizer)
				{
					dragFound = true;
					_dragAndDropDelegate = _dragAndDropDelegate ?? new DragAndDropDelegate(_handler);
					if (uIDragInteraction == null && PlatformView != null)
					{
						var interaction = new UIDragInteraction(_dragAndDropDelegate);
						interaction.Enabled = true;
						_interactions.Add(interaction);
						PlatformView.AddInteraction(interaction);
					}
				}
 
				if (OperatingSystem.IsIOSVersionAtLeast(11) && recognizer is DropGestureRecognizer)
				{
					dropFound = true;
					_dragAndDropDelegate = _dragAndDropDelegate ?? new DragAndDropDelegate(_handler);
					if (uIDropInteraction == null && PlatformView != null)
					{
						var interaction = new UIDropInteraction(_dragAndDropDelegate);
						_interactions.Add(interaction);
						PlatformView.AddInteraction(interaction);
					}
				}
 
				var nativeRecognizers = GetPlatformRecognizer(recognizer);
 
				if (nativeRecognizers is null)
					continue;
 
				_gestureRecognizers[recognizer] = nativeRecognizers;
				foreach (UIGestureRecognizer? nativeRecognizer in nativeRecognizers)
				{
					if (nativeRecognizer != null && PlatformView != null)
					{
						_proxy ??= new ShouldReceiveTouchProxy(this);
						nativeRecognizer.ShouldReceiveTouch = _proxy.ShouldReceiveTouch;
						PlatformView.AddGestureRecognizer(nativeRecognizer);
 
					}
				}
			}
 
			if (OperatingSystem.IsIOSVersionAtLeast(11))
			{
				if (!dragFound && uIDragInteraction != null && PlatformView != null)
					PlatformView.RemoveInteraction(uIDragInteraction);
 
				if (!dropFound && uIDropInteraction != null && PlatformView != null)
					PlatformView.RemoveInteraction(uIDropInteraction);
			}
 
			var toRemove = new List<IGestureRecognizer>();
 
			foreach (var key in _gestureRecognizers.Keys)
			{
				if (!ElementGestureRecognizers.Contains(key))
					toRemove.Add(key);
			}
 
			for (int i = 0; i < toRemove.Count; i++)
			{
				IGestureRecognizer gestureRecognizer = toRemove[i];
				var uiRecognizers = _gestureRecognizers[gestureRecognizer];
				_gestureRecognizers.Remove(gestureRecognizer);
 
				foreach (var uiRecognizer in uiRecognizers)
				{
					if (uiRecognizer is null)
						continue;
 
					if (PlatformView != null)
					{
						PlatformView.RemoveGestureRecognizer(uiRecognizer);
					}
 
					if (TryGetTapGestureRecognizer(gestureRecognizer, out TapGestureRecognizer? tapGestureRecognizer) &&
						tapGestureRecognizer != null)
					{
						gestureRecognizer.PropertyChanged -= OnTapGestureRecognizerPropertyChanged;
					}
 
					uiRecognizer.Dispose();
				}
			}
 
			if (PlatformView != null && OperatingSystem.IsIOSVersionAtLeast(11))
			{
				for (int i = PlatformView.Interactions.Length - 1; i >= 0; i--)
				{
					var interaction = (IUIInteraction)PlatformView.Interactions[i];
					if (interaction is FakeRightClickContextMenuInteraction && !_interactions.Contains(interaction))
					{
						PlatformView.RemoveInteraction(interaction);
					}
				}
			}
		}
 
		void OnTapGestureRecognizerPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			if (e.Is(TapGestureRecognizer.ButtonsProperty))
				LoadRecognizers();
		}
 
		class ShouldReceiveTouchProxy
		{
			readonly WeakReference<GesturePlatformManager> _manager;
 
			public ShouldReceiveTouchProxy(GesturePlatformManager manager) => _manager = new(manager);
 
			public bool ShouldReceiveTouch(UIGestureRecognizer recognizer, UITouch touch)
			{
				if (!_manager.TryGetTarget(out var manager))
					return false;
 
				var platformView = manager.PlatformView;
				var virtualView = manager._handler?.VirtualView;
 
				if (virtualView == null || platformView == null)
				{
					return false;
				}
 
				if (virtualView.InputTransparent)
				{
					return false;
				}
 
				if (!virtualView.IsEnabled)
				{
					return false;
				}
 
				if (touch.View == platformView)
				{
					return true;
				}
 
				// If the touch is coming from the UIView our handler is wrapping (e.g., if it's  
				// wrapping a UIView which already has a gesture recognizer), then we should let it through
				// (This goes for children of that control as well)
 
				if (touch.View.IsDescendantOfView(platformView) &&
					(touch.View.GestureRecognizers?.Length > 0 || platformView.GestureRecognizers?.Length > 0))
				{
					return true;
				}
 
				return false;
			}
		}
 
		void GestureRecognizersOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
		{
			if (PlatformView != null)
			{
				PlatformView.AccessibilityTraits &= ~_addedFlags;
 
				if (OperatingSystem.IsIOSVersionAtLeast(13) || OperatingSystem.IsMacCatalystVersionAtLeast(13))
				{
					if (_defaultAccessibilityRespondsToUserInteraction != null)
						PlatformView.AccessibilityRespondsToUserInteraction = _defaultAccessibilityRespondsToUserInteraction.Value;
				}
			}
 
			_addedFlags = UIAccessibilityTrait.None;
			_defaultAccessibilityRespondsToUserInteraction = null;
			LoadRecognizers();
		}
 
		void OnElementChanged(object sender, VisualElementChangedEventArgs e)
		{
			if (e.OldElement != null)
			{
				// unhook
				var oldView = e.OldElement as View;
				if (oldView != null)
				{
					var oldRecognizers = (ObservableCollection<IGestureRecognizer>)oldView.GestureRecognizers;
					oldRecognizers.CollectionChanged -= _collectionChangedHandler;
				}
			}
 
			if (e.NewElement != null)
			{
				// hook
				if (ElementGestureRecognizers != null)
				{
					ElementGestureRecognizers.CollectionChanged += _collectionChangedHandler;
					LoadRecognizers();
				}
			}
		}
 
		ButtonsMask AddFakeRightClickForMacCatalyst(IGestureRecognizer recognizer)
		{
			if (OperatingSystem.IsIOSVersionAtLeast(13, 4) && OperatingSystem.IsMacCatalyst())
			{
				TryGetTapGestureRecognizer(recognizer, out TapGestureRecognizer? tapRecognizer);
 
				if (tapRecognizer == null || (tapRecognizer.Buttons & ButtonsMask.Secondary) != ButtonsMask.Secondary)
				{
					return (ButtonsMask)0;
				}
 
				if (PlatformView != null)
				{
					foreach (var interaction in PlatformView.Interactions)
					{
						// check if this gesture was already added
						if (interaction is FakeRightClickContextMenuInteraction faker &&
							faker.TapGestureRecognizer == tapRecognizer)
						{
							_interactions.Add(faker);
							return tapRecognizer.Buttons;
						}
					}
				}
 
				var fakeInteraction = new FakeRightClickContextMenuInteraction(tapRecognizer, this);
				_interactions.Add(fakeInteraction);
 
				PlatformView?.AddInteraction(fakeInteraction);
 
				return tapRecognizer.Buttons;
			}
 
			return (ButtonsMask)0;
		}
 
		[SupportedOSPlatform("ios13.0")]
		[SupportedOSPlatform("maccatalyst13.0.0")]
		[UnsupportedOSPlatform("tvos")]
		internal class FakeRightClickContextMenuInteraction : UIContextMenuInteraction
		{
			// Store a reference to the platform delegate so that it is not garbage collected
			FakeRightClickDelegate? _dontCollectMePlease;
 
			public FakeRightClickContextMenuInteraction(TapGestureRecognizer tapGestureRecognizer, GesturePlatformManager gestureManager)
				: base(new FakeRightClickDelegate(tapGestureRecognizer, gestureManager))
			{
				_dontCollectMePlease = Delegate as FakeRightClickDelegate;
			}
 
			public TapGestureRecognizer? TapGestureRecognizer => _dontCollectMePlease?.TapGestureRecognizer;
 
			class FakeRightClickDelegate : UIContextMenuInteractionDelegate
			{
				WeakReference _recognizer;
				WeakReference _gestureManager;
 
				public TapGestureRecognizer? TapGestureRecognizer => _recognizer.Target as TapGestureRecognizer;
				public FakeRightClickDelegate(TapGestureRecognizer tapGestureRecognizer, GesturePlatformManager gestureManager)
				{
					_recognizer = new WeakReference(tapGestureRecognizer);
					_gestureManager = new WeakReference(gestureManager);
				}
 
				public override UIContextMenuConfiguration? GetConfigurationForMenu(UIContextMenuInteraction interaction, CGPoint location)
				{
					if (TapGestureRecognizer?.NumberOfTapsRequired == 1)
						ProcessRecognizerHandlerTap(_gestureManager, _recognizer, location, 1);
 
					return null;
				}
			}
		}
	}
}