将 TableView 的拖放 API 与基础值类型数据对象一起使用

Using TableView's Drag & Drop API with underlying value type data objects

在 iOS11 中,Apple 通过 NSItemProviderclass 并使用 UITableViewDragDelegateUITableViewDropDelegate.

为 UITableView 提供了自定义实现

除非我遗漏了某些东西(总是有可能!),否则在使用 tableView 的可区分数据源时,使用这些 API 的最佳方法是将基础 item 包装在从当前快照获得的数据源中,在 NSItemProvider 中,然后将其嵌入到 UIDragItem 中,以便通过委托方法进行拖放。特别是在拖动委托中是这样的:

extension DeliveryViewController :  UITableViewDragDelegate  {
   func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
      let itemType = UTType(exportedAs: "MyDataObject", conformingTo: .item).identifier
      let item = dataSource.itemIdentifier(for: indexPath)!
      let itemProvider = NSItemProvider(item: item, typeIdentifier: itemType)
      return [UIDragItem(itemProvider: itemProvider)]
   }
}

然而,要使用 NSItemProvider,底层数据对象需要符合 NSObjectNSItemProviderReadingNSItemProviderWriting。这是为常见的 NSxxx 数据类型提供的,但任何自定义数据对象都需要采用这些。如果您的数据对象是 Class,则遵守这些协议相对简单,但如果它是值类型则不是,因为这些是 class 协议。即使是本机 Swift 值类型也必须桥接到它们的遗留等价物(例如 StringNSStringDataNSData)才能与这些一起使用API。

这让我想到了问题的关键:我希望将这些拖放 API 引入到已建立的代码库中,其中底层数据对象都是值类型,原因很充分。出于多种原因,我不愿意将这些结构更改为 classes,包括引入新的错误和回归。

我考虑过(但尚未测试)只是将这些数据对象包装在 tableView 的视图模型中的 class 个对象中。让包装器 class 初始化程序采用原始结构并将其添加到 class 中的 属性。然后使这个包装器 Class 符合必要的协议。虽然这是一种开销,但我宁愿尽可能避免,因为这意味着更改 tableView 方法并实施一种方法来写回更广泛的数据模型中对底层结构的任何更改。另外,我还不知道在自定义 class 中支持 NSItemProvider 所需的 if 协议是否继承自值类型属性可能有问题的任何其他协议。

如果有人对如何最好地解决这个问题有任何建议,我们将不胜感激(请不要建议第三方库,因为这不是我想要追求的途径)。

在引入可区分数据源 API 之前,我在 Mac 方面 (NSTableView) 遇到过这个问题。

你不会喜欢听这个,但我完全建议你选择 类,100%。

如您所见,AppKit/UIKit 非常依赖 Objective C 围绕的“对象是具有身份的引用”概念。这些 API 早于 Identifiable 协议,尽管它几乎就像该协议隐式存在一样。每个 NSObject 都符合它,其中它的标识符始终是它的对象地址(对象标识)。

您可能有一些结构有意义地成为结构(因为它们是具有值相等且没有身份的简单值),但我认为一旦您将它们扔进 table 中,情况就会发生变化。在你这样做的那一刻,第 4 行的 Bob Smith 变得与第 7 行的 Bob Smith 不同,尽管它们的值相等。它们在视图中的位置赋予了它们新的身份维度,结构无法按原样处理。

更糟糕的是,许多这些身份相关的 API 使用 Any 而不是 AnyObject 导入。如果您导入了 Foundation,尝试将值类型传递给 Objective C API 将使 Swift 隐式地将它们装入 _SwiftValue 个对象。

这些对象对您来说是隐藏的,并且 created/destroyed 在幕后。与任何其他对象一样,它们具有基于其地址的已定义标识,但这不是您可以轻松访问的东西,当然也不是您可以依赖的东西。 (例如,如果你 return 相同的结构值两次,你将得到两个副本,每个都包含在不同的 _SwiftValue 中。这两个将是不同的)