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

Posted

技术标签:

【中文标题】将 TableView 的拖放 API 与底层值类型数据对象一起使用【英文标题】:Using TableView's Drag & Drop API with underlying value type data objects 【发布时间】:2021-11-09 12:53:38 【问题描述】:

ios 11 中,Apple 通过 NSItemProvider 类向操作系统引入了拖放 API(主要是支持在 iPadOS 上的应用程序之间拖动),并通过 UITableViewDragDelegateUITableViewDropDelegate 为 UITableView 提供了自定义实现。

除非我在处理 tableViews 的可区分数据源时遗漏了某些东西(总是可能的!),否则使用这些 API 的最佳方法是将底层 item 包装在从当前快照获得的数据源中,在 @987654325 中@ 然后将其嵌入到 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 数据类型提供的,但任何自定义数据对象都需要采用这些。如果您的数据对象是一个类,那么遵守这些协议相对简单,但如果它是一个值类型,则不是因为这些是类协议。即使是原生 Swift 值类型也必须桥接到它们的旧版等价物(例如,StringNSStringDataNSData)才能与这些 API 一起使用。

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

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

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

【问题讨论】:

【参考方案1】:

在引入 diffable 数据源 API 之前,我在 Mac 端 (NSTableView) 遇到了这个问题。

你不会喜欢听这个,但我完全建议你去上课,100%。

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

您可能有一些结构体作为结构体是有意义的(因为它们是具有值相等且没有身份的简单值),但我认为一旦将它们扔到表格中,情况就会发生变化。当您这样做时,第 4 行上的 Bob Smith 与第 7 行上的 Bob Smith 不同,尽管值相等。它们在视图中的位置为它们提供了一个新的身份维度,这是结构无法按原样处理的。

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

这些对象隐藏在远离您的地方,并在幕后被创建/销毁。与任何其他对象一样,它们具有基于其地址的定义身份,但这不是您可以轻松访问的东西,当然也不是您可以依赖的东西。 (例如,如果您两次返回相同的结构值,您将获得两个副本,每个副本都包装在不同的 _SwiftValue 中。两者将不相同)

【讨论】:

我有一种可怕的感觉,这就是我得到的答案!这些 API 真的感觉像是属于 iOS5 和 ObjC,而不是 iOS11+ 和 Swift。每年我都希望他们能得到重写,尤其是现在所有可区分的东西都基于Identifiable。也许 WWDC22 ......我会花点时间看看是否有人想出了灵丹妙药,但如果没有,我会(不情愿地?)接受你的回答。 公平地说,我认为即使有今天可用的结构,引用类型在这里仍然很合适。即使是可识别的,仍然存在引用语义,它只是碰巧是我用来键入字典的,而不是用于键入 RAM 的内存地址。 我可以同意...如果不是用于可区分的数据源,UIKit 表/集合视图的未来将基于 Identifiable 而不是参考标识,这与这使得将两组 API 合并到一个 tableView 中变得更加困难 是的。恐怕因为它们的版本要求,我还不能采用它们,恐怕 我整个星期都在与它作斗争,试图改造成旧的代码库,这很痛苦。它似乎还要求底层对象符合NSSecureCoding,这是我没想到的,而且是另一层复杂性。至少灰色的小细胞正在锻炼:-)

以上是关于将 TableView 的拖放 API 与底层值类型数据对象一起使用的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 iOS11 的拖放 API 获取正在拖动的单元格的框架或中心

JavaFX - 拖放 TableCell

HTML5 拖放 getData() 仅适用于 Chrome 中的拖放事件?

HTML5 拖放 getData() 仅适用于 Chrome 中的拖放事件?

iOS 中的拖放功能

如何启用普通和 UAC 提升权限应用程序之间的拖放