在 Xamarin.iOS 中进行拖放

为 iOS 11 实现拖放

iOS 11 支持拖放,以便在 iPad 上的应用程序之间复制数据。 用户可以从并排放置的应用中选择并拖动所有类型的内容,或者通过拖动应用图标来触发应用打开并允许删除数据:

将示例从自定义应用拖放到 Notes 应用中

注意

在 iOS 15 之前,拖放只能在 iPhone 上的同一应用中使用。 iOS 15 引入了跨应用拖放。

考虑在可以创建或编辑内容的任何位置支持拖放操作:

  • 文本控件支持针对 iOS 11 生成的所有应用的拖放,无需任何额外工作。
  • 表视图和集合视图包含 iOS 11 中的增强功能,可简化添加拖放行为。
  • 任何其他视图都可以通过额外的自定义来支持拖放。

向应用添加拖放支持时,可以提供不同级别的内容保真度;例如,可以提供数据的格式化文本和纯文本版本,以便接收应用可以选择最适合拖动目标的数据。 还可以自定义拖动可视化效果,并启用一次性拖动多个项目的设置。

使用文本控件进行拖放

UITextViewUITextField 自动支持拖出所选文本和放入文本内容。

使用 UITableView 进行拖放

UITableView 具有与表行拖放交互的内置处理,只需要几个方法即可启用默认行为。

涉及到两个接口:

  • IUITableViewDragDelegate – 在表视图中启动拖动时打包信息。
  • IUITableViewDropDelegate – 尝试并完成拖动时处理信息。

在示例中,这两个接口以及委托和数据源均在 UITableViewController 类上实现。 它们在 ViewDidLoad 方法中分配:

this.TableView.DragDelegate = this;
this.TableView.DropDelegate = this;

下面解释了这两个接口所需的最少代码。

表视图拖动委托

支持从表视图中拖动行所需的唯一方法是 GetItemsForBeginningDragSession。 如果用户开始拖动行,则会调用此方法。

下面显示了一个实现。 它检索与拖动的行关联的数据,对其进行编码,并配置一个 NSItemProvider 用于确定应用程序如何处理操作的“拖动”部分(例如,它们是否可以处理示例中的数据类型 PlainText):

public UIDragItem[] GetItemsForBeginningDragSession (UITableView tableView,
  IUIDragSession session, NSIndexPath indexPath)
{
  // gets the 'information' to be dragged
  var placeName = model.PlaceNames[indexPath.Row];
  // convert to NSData representation
  var data = NSData.FromString(placeName, NSStringEncoding.UTF8);
  // create an NSItemProvider to describe the data
  var itemProvider = new NSItemProvider();
  itemProvider.RegisterDataRepresentation(UTType.PlainText,
                                NSItemProviderRepresentationVisibility.All,
                                (completion) =>
  {
    completion(data, null);
    return null;
  });
  // wrap in a UIDragItem
  return new UIDragItem[] { new UIDragItem(itemProvider) };
}

拖动委托上有许多可选方法可以实现自定义拖动行为,例如提供可以在目标应用中利用的多种数据表示形式(例如格式化文本和纯文本,或者矢量和位图版本的绘图)。 还可以提供在同一应用中拖放时使用的自定义数据表示形式。

表视图拖放委托

在表视图上发生或完成拖动操作时,将调用放置委托上的方法。 所需的方法确定是否允许拖放数据,以及如果拖放完成则采取什么操作:

  • CanHandleDropSession – 当拖动正在进行并且可能被放置在应用上时,此方法确定是否允许放置正在拖动的数据。
  • DropSessionDidUpdate – 在拖动过程中,会调用此方法来确定要执行的操作。 来自被拖动的表视图、拖动会话和可能的索引路径的信息都可以用于确定提供给用户的行为和视觉反馈。
  • PerformDrop – 当用户完成拖放(通过抬起手指)时,此方法会提取正在拖动的数据并修改表格视图以将数据添加到新行(或多行)中。

CanHandleDropSession

CanHandleDropSession 指示表视图是否可以接受被拖动的数据。 在此代码片段中,CanLoadObjects 用于确认此表视图可以接受字符串数据。

public bool CanHandleDropSession(UITableView tableView, IUIDropSession session)
{
  return session.CanLoadObjects(typeof(NSString));
}

DropSessionDidUpdate

当拖动操作正在进行时,将反复调用 DropSessionDidUpdate 方法以向用户提供视觉提示。

在以下代码中,HasActiveDrag 用于确定操作是否源自当前表视图。 如果是,则只允许移动单行。 如果拖动操作来自其他源,则指示复制操作:

public UITableViewDropProposal DropSessionDidUpdate(UITableView tableView, IUIDropSession session, NSIndexPath destinationIndexPath)
{
  // The UIDropOperation.Move operation is available only for dragging within a single app.
  if (tableView.HasActiveDrag)
  {
    if (session.Items.Length > 1)
    {
        return new UITableViewDropProposal(UIDropOperation.Cancel);
    } else {
        return new UITableViewDropProposal(UIDropOperation.Move, UITableViewDropIntent.InsertAtDestinationIndexPath);
    }
  } else {
    return new UITableViewDropProposal(UIDropOperation.Copy, UITableViewDropIntent.InsertAtDestinationIndexPath);
  }
}

拖放操作可以是 CancelMoveCopy

拖放意图可以是插入新行,或将数据添加/追加到现有行。

PerformDrop

当用户完成操作时,将调用 PerformDrop 方法,并修改表视图和数据源以反映拖放的数据。

public void PerformDrop(UITableView tableView, IUITableViewDropCoordinator coordinator)
{
  NSIndexPath indexPath, destinationIndexPath;
  if (coordinator.DestinationIndexPath != null)
  {
    indexPath = coordinator.DestinationIndexPath;
    destinationIndexPath = indexPath;
  }
  else
  {
    // Get last index path of table view
    var section = tableView.NumberOfSections() - 1;
    var row = tableView.NumberOfRowsInSection(section);
    destinationIndexPath = NSIndexPath.FromRowSection(row, section);
  }
  coordinator.Session.LoadObjects(typeof(NSString), (items) =>
  {
    // Consume drag items
    List<string> stringItems = new List<string>();
    foreach (var i in items)
    {
      var q = NSString.FromHandle(i.Handle);
      stringItems.Add(q.ToString());
    }
    var indexPaths = new List<NSIndexPath>();
    for (var j = 0; j < stringItems.Count; j++)
    {
      var indexPath1 = NSIndexPath.FromRowSection(destinationIndexPath.Row + j, destinationIndexPath.Section);
      model.AddItem(stringItems[j], indexPath1.Row);
      indexPaths.Add(indexPath1);
    }
    tableView.InsertRows(indexPaths.ToArray(), UITableViewRowAnimation.Automatic);
  });
}

可以添加额外的代码来异步加载大型数据对象。