iOS 11上的拖放基础

iOS 11引入了通过拖放与应用程序中的数据进行交互的新方式。 拖放允许用户点击并按住UI元素,然后将其拖动到其他位置以执行操作,例如将与该UI元素关联的数据移动到新位置,创建其副本或将其删除。 在iPad上,可以在单独的应用程序之间执行此交互,从而为用户与多个应用程序进行交互提供了一种强大的新方式,并允许轻松,直观地复制数据。

拖放状态的人机交互准则用户希望该行为“在整个系统中普遍实现”。 如果是这样,通过使应用程序能够以以前无法实现的方式进行通信,可以完全改变使用多个应用程序进行多任务处理的动态。

在为Trade Me应用程序添加一些基本的拖放支持时,我对它的实现如此简单感到惊讶,并且我认为分享其工作原理的简单概述可能会很有用。

实施阻力

当用户长按想要移动或复制(或对其执行某些其他独特操作)的某些内容时,便开始拖动,该内容可能是UICollectionViewUITableView或我们应用程序中的其他内容。 在此示例中,假设我们的内容是一个集合视图,每个单元格代表一个相册中的一张照片,并且我们希望允许用户复制它。

为了告诉系统我们的集合视图内容是可拖动的,我们必须在集合视图上设置dragDelegate属性,并在UICollectionViewDragDelegate协议上实现collectionView(_:itemsForBeginning:at:)函数。

我们的collectionView(_:itemsForBeginning:at:)必须返回一个包含一个或多个UIDragItem实例的UIDragItem ,这些实例负责提供表示要拖动的项目的数据。

UIDragItem实例通过UIDragItem实例提供其数据,该实例用于通过NSItemProviderWritingNSItemProviderReading协议在两个进程之间异步传送数据。

Apple提供的几种类型已经符合这些协议,因此,它们几乎可以在任何两个iOS应用之间传递,而在发送或接收应用中无需添加代码。 这些类型是NSStringNSAttributedStringNSURLUIColorUIImage

在我们的示例中,被拖动的内容是一张照片,因此我们可以使用UIImage来创建NSItemProvider的实例,该UIImage符合NSItemProviderWriting 。 这意味着我们无需编写任何代码即可将模型序列化为NSData表示形式,并且任何接受UIImage内容的应用程序都将允许用户将我们的内容放入其中。

值得注意的是,直到删除发生,系统才真正请求拖动的数据。 此时,系统会在源应用程序内的模型对象上调用loadData(withTypeIdentifier:forItemProviderCompletionHandler:)

实现下降

现在,我们已经完成了实现的拖动部分,让我们跳到接收应用程序,看看drop的实现是什么样子。 为了使我们的示例简单起见,假设用户将照片拖放到接收应用程序中的类似收藏夹视图中。

为了接收删除的内容,我们的集合视图必须设置其dropDelegate属性,并且必须实现UICollectionViewDropDelegate函数collectionView(_:performDropWith:) 。 此功能负责异步地从源应用程序中获取数据(如果拖动源自另一个应用程序,否则它可以同步发生),并将新内容设置为视图层次结构的动画。

我们的collectionView(_:performDropWith:)只需要迭代提供的UICollectionViewDropCoordinatoritems ,并通过每个UIDragItem的项目提供程序上的loadData(withTypeIdentifier:forItemProviderCompletionHandler:)函数从源应用程序中获取相应的数据。 由于此操作是异步操作,因此在系统从原始应用程序请求数据并向用户提供反馈的同时,我们应适当地处理加载时间,这一点很重要。

我们可以在UICollectionViewDropCoordinator上使用drop(_:to:)函数为新内容创建一个占位符单元格。 加载完成后,我们只需要更新数据源并在新内容中设置动画即可。 我们可以使用创建占位符时返回的UICollectionViewDropPlaceholderContext上的commitInsertion(dataSourceUpdates:)函数来完成此操作。

我们的collectionView(_:performDropWith:)也应该能够处理源自同一应用程序的内容。 在这种情况下,我们可以简单地使用localObject属性将新内容插入到我们的数据源中。

有用的提示

我们要做的就是允许我们的两个应用程序之间进行基本的拖放交互,但是对于那些仍在阅读的人来说,这是我在探索这些API时发现的一些窍门。

  • NSURL是系统在本机支持下在应用程序之间传递数据的重要后备,并且可以通过通用链接链接到Web上或应用程序内的内容。
  • localObject属性仅在拖动项所源自的应用程序中可见,但对于轻松地将模型对象与拖动项一起传递到应用程序中很有用。 如果您要实现专门用于在iPhone上进行拖放操作,并且该交互仅限于一个应用程序,那么您可以只使用localObject而不必担心NSItemProviderWritingNSItemProviderReading
  • 实现collectionView(_:dropSessionDidUpdate:withDestinationIndexPath:)函数以返回UICollectionViewDropProposal ,该信号指示将内容放置在给定位置时将采取何种操作,以便系统可以向用户提供视觉反馈。
  • 通过使用Apple提供的编码器和解码器(如JSONEncoderJSONDecoder ,拥有符合Codable模型类型可以非常轻松地为NSItemProviderWritingNSItemProviderReading编写实现。
  • NSItemProviderWriting要求您为可通过writableTypeIdentifiersForItemProvider静态变量表示内容的不同格式指定标识符。 这些标识符确定其他应用如何识别和处理您的内容。 您可以使用MobileCoreServices框架中定义的统一类型标识符(UTI)来指定并非您的应用程序唯一的类型。 一个示例是kUTTypeUTF8PlainText ,我可以使用它来为我的一个自定义模型指定一个后备表示形式,以使其可以作为String复制到其他应用程序中。
  • 假设存在代表这些数据的通用标准,那么您应该能够使用MobileCoreServices UTI支持更复杂的数据。 您可以支持的更复杂数据类型的一个示例是通过vCard格式的联系信息。 在这种情况下,您可以将类型标识符指定为kUTTypeVCard并使用Contact框架的CNContactVCardSerialization类对数据进行序列化/反序列化。

要查看拖放的基本端到端实现,请在github上查看我的示例应用程序:https://github.com/RowbotNZ/DragonDrop

您想从Trade Me之类的应用程序中看到什么样的拖放交互? 将您的建议发给我@RowbotNZ,我将拭目以待!