iOS 11上的拖放基础
iOS 11引入了通过拖放与应用程序中的数据进行交互的新方式。 拖放允许用户点击并按住UI元素,然后将其拖动到其他位置以执行操作,例如将与该UI元素关联的数据移动到新位置,创建其副本或将其删除。 在iPad上,可以在单独的应用程序之间执行此交互,从而为用户与多个应用程序进行交互提供了一种强大的新方式,并允许轻松,直观地复制数据。
拖放状态的人机交互准则用户希望该行为“在整个系统中普遍实现”。 如果是这样,通过使应用程序能够以以前无法实现的方式进行通信,可以完全改变使用多个应用程序进行多任务处理的动态。
在为Trade Me应用程序添加一些基本的拖放支持时,我对它的实现如此简单感到惊讶,并且我认为分享其工作原理的简单概述可能会很有用。
实施阻力
当用户长按想要移动或复制(或对其执行某些其他独特操作)的某些内容时,便开始拖动,该内容可能是UICollectionView
, UITableView
或我们应用程序中的其他内容。 在此示例中,假设我们的内容是一个集合视图,每个单元格代表一个相册中的一张照片,并且我们希望允许用户复制它。
为了告诉系统我们的集合视图内容是可拖动的,我们必须在集合视图上设置dragDelegate
属性,并在UICollectionViewDragDelegate
协议上实现collectionView(_:itemsForBeginning:at:)
函数。
我们的collectionView(_:itemsForBeginning:at:)
必须返回一个包含一个或多个UIDragItem
实例的UIDragItem
,这些实例负责提供表示要拖动的项目的数据。
UIDragItem
实例通过UIDragItem
实例提供其数据,该实例用于通过NSItemProviderWriting
和NSItemProviderReading
协议在两个进程之间异步传送数据。
Apple提供的几种类型已经符合这些协议,因此,它们几乎可以在任何两个iOS应用之间传递,而在发送或接收应用中无需添加代码。 这些类型是NSString
, NSAttributedString
, NSURL
, UIColor
和UIImage
。
在我们的示例中,被拖动的内容是一张照片,因此我们可以使用UIImage
来创建NSItemProvider
的实例,该UIImage
符合NSItemProviderWriting
。 这意味着我们无需编写任何代码即可将模型序列化为NSData
表示形式,并且任何接受UIImage
内容的应用程序都将允许用户将我们的内容放入其中。
值得注意的是,直到删除发生,系统才真正请求拖动的数据。 此时,系统会在源应用程序内的模型对象上调用loadData(withTypeIdentifier:forItemProviderCompletionHandler:)
。
实现下降
现在,我们已经完成了实现的拖动部分,让我们跳到接收应用程序,看看drop的实现是什么样子。 为了使我们的示例简单起见,假设用户将照片拖放到接收应用程序中的类似收藏夹视图中。
为了接收删除的内容,我们的集合视图必须设置其dropDelegate
属性,并且必须实现UICollectionViewDropDelegate
函数collectionView(_:performDropWith:)
。 此功能负责异步地从源应用程序中获取数据(如果拖动源自另一个应用程序,否则它可以同步发生),并将新内容设置为视图层次结构的动画。
我们的collectionView(_:performDropWith:)
只需要迭代提供的UICollectionViewDropCoordinator
的items
,并通过每个UIDragItem
的项目提供程序上的loadData(withTypeIdentifier:forItemProviderCompletionHandler:)
函数从源应用程序中获取相应的数据。 由于此操作是异步操作,因此在系统从原始应用程序请求数据并向用户提供反馈的同时,我们应适当地处理加载时间,这一点很重要。
我们可以在UICollectionViewDropCoordinator
上使用drop(_:to:)
函数为新内容创建一个占位符单元格。 加载完成后,我们只需要更新数据源并在新内容中设置动画即可。 我们可以使用创建占位符时返回的UICollectionViewDropPlaceholderContext
上的commitInsertion(dataSourceUpdates:)
函数来完成此操作。
我们的collectionView(_:performDropWith:)
也应该能够处理源自同一应用程序的内容。 在这种情况下,我们可以简单地使用localObject
属性将新内容插入到我们的数据源中。
有用的提示
我们要做的就是允许我们的两个应用程序之间进行基本的拖放交互,但是对于那些仍在阅读的人来说,这是我在探索这些API时发现的一些窍门。
-
NSURL
是系统在本机支持下在应用程序之间传递数据的重要后备,并且可以通过通用链接链接到Web上或应用程序内的内容。 -
localObject
属性仅在拖动项所源自的应用程序中可见,但对于轻松地将模型对象与拖动项一起传递到应用程序中很有用。 如果您要实现专门用于在iPhone上进行拖放操作,并且该交互仅限于一个应用程序,那么您可以只使用localObject
而不必担心NSItemProviderWriting
和NSItemProviderReading
。 - 实现
collectionView(_:dropSessionDidUpdate:withDestinationIndexPath:)
函数以返回UICollectionViewDropProposal
,该信号指示将内容放置在给定位置时将采取何种操作,以便系统可以向用户提供视觉反馈。 - 通过使用Apple提供的编码器和解码器(如
JSONEncoder
和JSONDecoder
,拥有符合Codable
模型类型可以非常轻松地为NSItemProviderWriting
和NSItemProviderReading
编写实现。 -
NSItemProviderWriting
要求您为可通过writableTypeIdentifiersForItemProvider
静态变量表示内容的不同格式指定标识符。 这些标识符确定其他应用如何识别和处理您的内容。 您可以使用MobileCoreServices
框架中定义的统一类型标识符(UTI)来指定并非您的应用程序唯一的类型。 一个示例是kUTTypeUTF8PlainText
,我可以使用它来为我的一个自定义模型指定一个后备表示形式,以使其可以作为String
复制到其他应用程序中。 - 假设存在代表这些数据的通用标准,那么您应该能够使用
MobileCoreServices
UTI支持更复杂的数据。 您可以支持的更复杂数据类型的一个示例是通过vCard格式的联系信息。 在这种情况下,您可以将类型标识符指定为kUTTypeVCard
并使用Contact
框架的CNContactVCardSerialization
类对数据进行序列化/反序列化。
要查看拖放的基本端到端实现,请在github上查看我的示例应用程序:https://github.com/RowbotNZ/DragonDrop
您想从Trade Me之类的应用程序中看到什么样的拖放交互? 将您的建议发给我@RowbotNZ,我将拭目以待!