本文将分享如何实现 Table View 的 Drag & Drop 排序,如何 Open Gap for Drag Destination 和实现过程中遇到的问题及相应解决方案。
Dock 拖拽排序效果(Open Gap for Drag Destination):
在 CurrencyX 1.3 版本中,我们增加了汇率列表可拖拽排序功能,实现过程很简单;在优化 Drop 效果为 Open Gap 时(即类似 Dock 上拖拽图标排序的效果)遇到了 Row 不断抖动的问题,最终偶然发现是因为仅设置了 rowHeight 属性而没有实现 Table View Delegate 中 Row Height 相关的方法导致的。
首先设置 Table View 可以接受的 Drag Type,在这里我们对 Currency 排序使用的是 Currency Code,Type 为 NSStringPboardType:
let registeredTypes:[String] = [NSStringPboardType] tableView.registerForDraggedTypes(registeredTypes)
在用户 Drag 时,将需要的信息写到剪贴板上:
func tableView(tableView: NSTableView, writeRowsWithIndexes rowIndexes: NSIndexSet, toPasteboard pboard: NSPasteboard) -> Bool { let currencyCode = // Get Currency Code At Row let registeredTypes:[String] = [NSStringPboardType] pboard.declareTypes(registeredTypes, owner: self) pboard.setString(currencyCode, forType: NSStringPboardType) return true }
在 Drop 时,首先根据用户拖拽 Row 的位置来返回适当的操作,在这里,我们希望仅当用户插入两行之间时执行 Move 操作:
func tableView(tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation { if (dropOperation == .Above) { return .Move } return .None; }
对于 Valid Drop 操作,通过实现下述方法获取剪贴板上的信息并依次进行数据排序,更新 Table View:
func tableView(tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool { if let currencyCode = info.draggingPasteboard().stringForType(NSStringPboardType), let oldIndex = userCurrencyCodes.indexOf(currencyCode) { // Update Data Source // Update Table View return true } else { return false } }
至此,我们已经完成了 Table View 的 Drag & Drop 排序。
同时我们也正在开发 Today Extension,发现系统自带的排序操作起来用户体验更好一些。
我们也决定使用这样的 Drop 效果,实现起来也很简单,只需要对 Table View 进行如下设置即可:
tableView.draggingDestinationFeedbackStyle = .Gap
然而运行起来得到的结果是可怕的:
首先我们新建了一个测试工程,创建了一个最简单的 Table View 并将 Drag & Drop 相关的代码加入其中,发现仍然会出现拖拽时整个 View 抖动或者某些 Row 弹跳的问题。确定引发问题的原因与我们自定义的 NSTableCellView 没有关系。
接着仔细察看文档和 Google 到如何实现“Open Gap for Drag & Drop”的相关文章,我们的实现似乎并没有问题。
在 Google 时发现 StackOverflow 上有人提到 NSTableView 的 Dragging Destination Feedback Style 是 OS X 10.9 Mavericks 之后才提供的 API,并 给出了针对更早系统自定义的实现方式 。基本思路是在用户 Drag 到某个 Row 上时,使用自定义 Table View 的 noteHeightOfRowsWithIndexesChanged
方法修改 Row Height 来实现的。猜测系统 API 的实现方式应该也无太大区别。
联想到我们的代码中,仅在 Table View 初始化时设置了 rowHeight
属性,拖拽时抖动的表现可能是 Table View 无法获取到 Row 正确的高度。于是实现了 Delegate 方法返回 Row Height:
func tableView(tableView: NSTableView, heightOfRow row: Int) -> CGFloat
再次运行便成功了:tada:。
此外,在 Drop Destination Feedback Style 为 Gap 时,为了获得更好的用户体验,可以修改 Validate Drop 函数在 dropOperation 为 .Above 或 .On 的时候都 return .Move
,这样也和 Extension 中系统的默认行为更加接近。
Happy Coding :beer:
顺便看了一眼毫无进度的某下载软件,拖拽时也有些颤~抖。
SalesX 是给 Apple 开发者使用的菜单栏工具,第一时间把 app 销售情况推送给你,7 天免费试用
CurrencyX 是 Mac 上小而美的汇率 app
如果你觉得文章对你有帮助,可以买一个支持我们
关注我们公众号,获取最新文章推送