|
#nullable disable
using System;
using System.Collections;
using System.Collections.Specialized;
using Foundation;
using ObjCRuntime;
using UIKit;
namespace Microsoft.Maui.Controls.Handlers.Items
{
public class ReorderableItemsViewController<TItemsView> : GroupableItemsViewController<TItemsView>
where TItemsView : ReorderableItemsView
{
bool _disposed;
UILongPressGestureRecognizer _longPressGestureRecognizer;
public ReorderableItemsViewController(TItemsView reorderableItemsView, ItemsViewLayout layout)
: base(reorderableItemsView, layout)
{
// The UICollectionViewController has built-in recognizer for reorder that can be installed by setting "InstallsStandardGestureForInteractiveMovement".
// For some reason it only seemed to work when the CollectionView was inside the Flyout section of a FlyoutPage.
// The UILongPressGestureRecognizer is simple enough to set up so let's just add our own.
InstallsStandardGestureForInteractiveMovement = false;
}
public override bool CanMoveItem(UICollectionView collectionView, NSIndexPath indexPath)
{
return ItemsView?.CanReorderItems == true;
}
protected override UICollectionViewDelegateFlowLayout CreateDelegator()
{
return new ReorderableItemsViewDelegator<TItemsView, ReorderableItemsViewController<TItemsView>>(ItemsViewLayout, this);
}
protected override IItemsViewSource CreateItemsViewSource()
{
if (ItemsSource != null)
{
// There's a bug in the current Maui Controls library.
// It will call "CreateItemsViewSource" 2x in a row when opening a page.
// It's invoked from both ViewDidLoad & UpdateItemsSource
// For the time being, until the issue is fixed, we need to dispose of the current source if one already exist.
ItemsSource.Dispose();
}
return base.CreateItemsViewSource();
}
protected override void Dispose(bool disposing)
{
if (disposing && !_disposed)
{
if (_longPressGestureRecognizer != null)
{
CollectionView.RemoveGestureRecognizer(_longPressGestureRecognizer);
_longPressGestureRecognizer.Dispose();
_longPressGestureRecognizer = null;
}
_disposed = true;
}
base.Dispose(disposing);
}
void HandleLongPress(UILongPressGestureRecognizer gestureRecognizer)
{
var collectionView = CollectionView;
if (collectionView == null)
return;
var location = gestureRecognizer.LocationInView(collectionView);
// We are updating "CancelsTouchesInView" so views can still receive touch events when this gesture runs.
// Those events shouldn't be aborted until they've actually moved the position of the CollectionView item.
switch (gestureRecognizer.State)
{
case UIGestureRecognizerState.Began:
var indexPath = collectionView?.IndexPathForItemAtPoint(location);
if (indexPath == null)
{
return;
}
gestureRecognizer.CancelsTouchesInView = false;
collectionView.BeginInteractiveMovementForItem(indexPath);
break;
case UIGestureRecognizerState.Changed:
gestureRecognizer.CancelsTouchesInView = true;
collectionView.UpdateInteractiveMovement(location);
break;
case UIGestureRecognizerState.Ended:
collectionView.EndInteractiveMovement();
break;
default:
collectionView.CancelInteractiveMovement();
break;
}
}
public override void MoveItem(UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
{
var itemsSource = ItemsSource;
var itemsView = ItemsView;
if (itemsSource == null || itemsView == null)
{
return;
}
if (itemsView.IsGrouped)
{
var fromList = itemsSource.Group(sourceIndexPath) as IList;
var fromItemsSource = fromList is INotifyCollectionChanged ? itemsSource.GroupItemsViewSource(sourceIndexPath) : null;
var fromItemIndex = sourceIndexPath.Row;
var toList = itemsSource.Group(destinationIndexPath) as IList;
var toItemsSource = toList is INotifyCollectionChanged ? itemsSource.GroupItemsViewSource(destinationIndexPath) : null;
var toItemIndex = destinationIndexPath.Row;
if (fromList != null && toList != null)
{
var fromItem = fromList[fromItemIndex];
SetObserveChanges(fromItemsSource, false);
SetObserveChanges(toItemsSource, false);
fromList.RemoveAt(fromItemIndex);
toList.Insert(toItemIndex, fromItem);
SetObserveChanges(fromItemsSource, true);
SetObserveChanges(toItemsSource, true);
itemsView.SendReorderCompleted();
}
}
else if (itemsView.ItemsSource is IList list)
{
var fromPosition = sourceIndexPath.Row;
var toPosition = destinationIndexPath.Row;
var fromItem = list[fromPosition];
SetObserveChanges(itemsSource, false);
list.RemoveAt(fromPosition);
list.Insert(toPosition, fromItem);
SetObserveChanges(itemsSource, true);
itemsView.SendReorderCompleted();
}
}
void SetObserveChanges(IItemsViewSource itemsSource, bool enable)
{
if (itemsSource is IObservableItemsViewSource observableSource)
{
observableSource.ObserveChanges = enable;
}
}
public void UpdateCanReorderItems()
{
if (ItemsView.CanReorderItems)
{
if (_longPressGestureRecognizer == null)
{
_longPressGestureRecognizer = new UILongPressGestureRecognizer(HandleLongPress);
CollectionView.AddGestureRecognizer(_longPressGestureRecognizer);
}
}
else
{
if (_longPressGestureRecognizer != null)
{
CollectionView.RemoveGestureRecognizer(_longPressGestureRecognizer);
_longPressGestureRecognizer.Dispose();
_longPressGestureRecognizer = null;
}
}
}
}
} |