File: iOS\DragAndDropDelegate.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)

#if __MOBILE__
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Versioning;
using System.Text;
using Foundation;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Platform;
using ObjCRuntime;
using UIKit;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS
{
	[SupportedOSPlatform("ios11.0")]
	internal class DragAndDropDelegate : NSObject, IUIDragInteractionDelegate, IUIDropInteractionDelegate
	{
		[Export("dragInteraction:session:willEndWithOperation:")]
		[Preserve(Conditional = true)]
		public void SessionWillEnd(UIDragInteraction interaction, IUIDragSession session, UIDropOperation operation)
		{
			if ((operation == UIDropOperation.Cancel || operation == UIDropOperation.Forbidden) &&
				session.Items.Length > 0 &&
				session.Items[0].LocalObject is CustomLocalStateData cdi)
			{
				this.HandleDropCompleted(cdi.View);
			}
		}
 
		[Preserve(Conditional = true)]
		public UIDragItem[] GetItemsForBeginningSession(UIDragInteraction interaction, IUIDragSession session)
		{
			if (interaction.View is IVisualElementRenderer renderer && renderer.Element is View view)
				return HandleDragStarting(view, renderer);
 
			return Array.Empty<UIDragItem>();
		}
 
		[Export("dropInteraction:canHandleSession:")]
		[Preserve(Conditional = true)]
		public bool CanHandleSession(UIDropInteraction interaction, IUIDropSession session)
		{
			if (session.LocalDragSession == null)
				return false;
 
			if (session.LocalDragSession.Items.Length > 0 &&
				session.LocalDragSession.Items[0].LocalObject is CustomLocalStateData)
			{
				return true;
			}
 
			return false;
		}
 
		[Export("dropInteraction:sessionDidExit:")]
		[Preserve(Conditional = true)]
		public void SessionDidExit(UIDropInteraction interaction, IUIDropSession session)
		{
			if (interaction.View is IVisualElementRenderer renderer)
			{
				DataPackage package = null;
 
				if (session.LocalDragSession.Items.Length > 0 &&
					session.LocalDragSession.Items[0].LocalObject is CustomLocalStateData cdi)
				{
					package = cdi.DataPackage;
				}
 
				if (HandleDragLeave((View)renderer.Element, package))
				{
				}
			}
		}
 
		[Export("dropInteraction:sessionDidUpdate:")]
		[Preserve(Conditional = true)]
		public UIDropProposal SessionDidUpdate(UIDropInteraction interaction, IUIDropSession session)
		{
			UIDropOperation operation = UIDropOperation.Cancel;
 
			if (session.LocalDragSession == null)
				return new UIDropProposal(operation);
 
			if (interaction.View is IVisualElementRenderer renderer)
			{
				DataPackage package = null;
 
				if (session.LocalDragSession.Items.Length > 0 &&
					session.LocalDragSession.Items[0].LocalObject is CustomLocalStateData cdi)
				{
					package = cdi.DataPackage;
				}
 
				if (HandleDragOver((View)renderer.Element, package))
				{
					operation = UIDropOperation.Copy;
				}
			}
 
			return new UIDropProposal(operation);
		}
 
		[Export("dropInteraction:performDrop:")]
		[Preserve(Conditional = true)]
		public void PerformDrop(UIDropInteraction interaction, IUIDropSession session)
		{
			if (session.LocalDragSession == null)
				return;
 
			if (session.LocalDragSession.Items.Length > 0 &&
				session.LocalDragSession.Items[0].LocalObject is CustomLocalStateData cdi &&
				interaction.View is IVisualElementRenderer renderer &&
				renderer.Element is View view)
			{
				HandleDrop(view, cdi.DataPackage);
				HandleDropCompleted(cdi.View);
			}
		}
 
 
		void SendEventArgs<TRecognizer>(Action<TRecognizer> func, View view)
			where TRecognizer : class
		{
			var gestures =
				view.GestureRecognizers;
 
			if (gestures == null)
				return;
 
			foreach (var gesture in gestures)
			{
				if (gesture is TRecognizer recognizer)
					func(recognizer);
			}
		}
 
		public UIDragItem[] HandleDragStarting(View element, IVisualElementRenderer renderer)
		{
			UIDragItem[] returnValue = null;
			SendEventArgs<DragGestureRecognizer>(rec =>
			{
				if (!rec.CanDrag)
					return;
 
				var args = rec.SendDragStarting(element);
 
				if (args.Cancel)
					return;
#pragma warning disable CS0618 // Type or member is obsolete
				if (!args.Handled)
#pragma warning restore CS0618 // Type or member is obsolete
				{
					UIImage uIImage = null;
					string clipDescription = String.Empty;
					NSItemProvider itemProvider = null;
 
					if (renderer is IImageVisualElementRenderer iver)
					{
						uIImage = iver.GetImage()?.Image;
						if (uIImage != null)
							itemProvider = new NSItemProvider(uIImage);
						else
							itemProvider = new NSItemProvider(new NSString(""));
 
						if (args.Data.Image == null && renderer.Element is IImageElement imageElement)
							args.Data.Image = imageElement.Source;
					}
					else
					{
						string text = args.Data.Text ?? clipDescription;
 
						if (String.IsNullOrWhiteSpace(text))
						{
							itemProvider = new NSItemProvider(renderer.NativeView.ConvertToImage());
						}
						else
						{
							itemProvider = new NSItemProvider(new NSString(text));
						}
					}
 
					var dragItem = new UIDragItem(itemProvider);
					dragItem.LocalObject = new CustomLocalStateData()
					{
						Renderer = renderer,
						View = renderer.Element as View,
						DataPackage = args.Data
					};
 
					returnValue = new UIDragItem[] { dragItem };
				}
			},
			element);
 
			return returnValue ?? Array.Empty<UIDragItem>();
		}
 
		void HandleDropCompleted(View element)
		{
			var args = new DropCompletedEventArgs();
			SendEventArgs<DragGestureRecognizer>(rec => rec.SendDropCompleted(args), element);
		}
 
		bool HandleDragLeave(View element, DataPackage dataPackage)
		{
			var dragEventArgs = new DragEventArgs(dataPackage);
 
			bool validTarget = false;
			SendEventArgs<DropGestureRecognizer>(rec =>
			{
				if (!rec.AllowDrop)
					return;
 
				rec.SendDragLeave(dragEventArgs);
				validTarget = validTarget || dragEventArgs.AcceptedOperation != DataPackageOperation.None;
			}, element);
 
			return validTarget;
		}
 
		bool HandleDragOver(View element, DataPackage dataPackage)
		{
			var dragEventArgs = new DragEventArgs(dataPackage);
 
			bool validTarget = false;
			SendEventArgs<DropGestureRecognizer>(rec =>
			{
				if (!rec.AllowDrop)
					return;
 
				rec.SendDragOver(dragEventArgs);
				validTarget = validTarget || dragEventArgs.AcceptedOperation != DataPackageOperation.None;
			}, element);
 
			return validTarget;
		}
 
		void HandleDrop(View element, DataPackage datapackage)
		{
			var args = new DropEventArgs(datapackage?.View);
			SendEventArgs<DropGestureRecognizer>(async rec =>
			{
				if (!rec.AllowDrop)
					return;
 
				try
				{
					await rec.SendDrop(args);
				}
				catch (Exception e)
				{
					Forms.MauiContext?.CreateLogger<DropGestureRecognizer>()?.LogWarning(e, null);
				}
			}, (View)element);
		}
 
		class CustomLocalStateData : NSObject
		{
			public View View { get; set; }
			public IVisualElementRenderer Renderer { get; set; }
			public DataPackage DataPackage { get; set; }
		}
	}
 
}
#endif