macos 在 NSTableView 上拖放重新排序行
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2121907/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Drag & Drop Reorder Rows on NSTableView
提问by Jared Pochtar
I was just wondering if there was an easy way to set an NSTableView to allow it to reorder its rows without writing any pasteboard code. I only need it to be able to do this internally, within one table. I have no issue writing the pboard code, except that I'm fairly sure that I saw Interface Builder have a toggle for this somewhere / saw it working by default. It certainly seems like a common enough task.
我只是想知道是否有一种简单的方法来设置 NSTableView 以允许它重新排序其行而无需编写任何粘贴板代码。我只需要它能够在一张桌子内在内部执行此操作。我在编写 pboard 代码时没有问题,除了我相当确定我看到 Interface Builder 在某处有一个切换开关/看到它默认工作。这当然似乎是一项足够常见的任务。
Thanks
谢谢
采纳答案by FluffulousChimp
If you take a look at the tool tip in IB you'll see that the option you refer to
如果您查看 IB 中的工具提示,您会看到您所指的选项
- (BOOL)allowsColumnReordering
controls, well, column reordering. I do not believe there is any other way to do this other than the standard drag-and-drop API for table views.
控制,好吧,列重新排序。我认为除了表视图的标准拖放 API 之外,没有其他任何方法可以做到这一点。
EDIT:( 2012-11-25 )
编辑:(2012-11-25)
The answer refers to drag-and-drop reordering of NSTableViewColumns
; and while it was the accepted answer at the time. It does not appear, now nearly 3 years on, to be correct. In service of making the information useful to searchers, I'll attempt to give the more correct answer.
答案是指拖放重新排序NSTableViewColumns
; 虽然这是当时公认的答案。现在将近 3 年过去了,它似乎并不正确。为了使信息对搜索者有用,我将尝试给出更正确的答案。
There is no setting that allows drag and drop reordering of NSTableView
rows in Interface Builder. You need to implement certain NSTableViewDataSource
methods, including:
没有允许NSTableView
在 Interface Builder 中拖放行重新排序的设置。您需要实现某些NSTableViewDataSource
方法,包括:
- tableView:acceptDrop:row:dropOperation:
- (NSDragOperation)tableView:(NSTableView *)aTableView validateDrop:(id < NSDraggingInfo >)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation
- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
There are other SO question that address this reasonably thoroughly, including this one
还有其他 SO 问题可以合理彻底地解决这个问题,包括这个问题
Apple link to Drag and Drop APIs.
Apple 链接到拖放 API。
回答by Ethan
Set your table view's datasource to be a class that conforms to NSTableViewDataSource
.
将表视图的数据源设置为符合NSTableViewDataSource
.
Put this in an appropriate place (-applicationWillFinishLaunching
, -awakeFromNib
, -viewDidLoad
or something similar):
将这个在适当的地方(-applicationWillFinishLaunching
,-awakeFromNib
,-viewDidLoad
或类似的东西):
tableView.registerForDraggedTypes(["public.data"])
Then implement these three NSTableViewDataSource
methods:
然后实现这三个NSTableViewDataSource
方法:
tableView:pasteboardWriterForRow:
tableView:validateDrop:proposedRow:proposedDropOperation:
tableView:acceptDrop:row:dropOperation:
Here is fully-working code that supports drag-and-drop reordering multiple rows:
这是支持拖放重新排序多行的完整代码:
func tableView(tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
let item = NSPasteboardItem()
item.setString(String(row), forType: "public.data")
return item
}
func tableView(tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
if dropOperation == .Above {
return .Move
}
return .None
}
func tableView(tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
var oldIndexes = [Int]()
info.enumerateDraggingItemsWithOptions([], forView: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
if let str = (func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
let item = NSPasteboardItem()
item.setString(String(row), forType: "private.table-row")
return item
}
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
if dropOperation == .above {
return .move
}
return []
}
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
var oldIndexes = [Int]()
info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
if let str = (private var dragDropType = NSPasteboard.PasteboardType(rawValue: "private.table-row")
override func viewDidLoad() {
super.viewDidLoad()
myTableView.delegate = self
myTableView.dataSource = self
myTableView.registerForDraggedTypes([dragDropType])
}
.0.item as! NSPasteboardItem).string(forType: "private.table-row"), let index = Int(str) {
oldIndexes.append(index)
}
}
var oldIndexOffset = 0
var newIndexOffset = 0
// For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
// You may want to move rows in your content array and then call `tableView.reloadData()` instead.
tableView.beginUpdates()
for oldIndex in oldIndexes {
if oldIndex < row {
tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
oldIndexOffset -= 1
} else {
tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
newIndexOffset += 1
}
}
tableView.endUpdates()
return true
}
.0.item as! NSPasteboardItem).stringForType("public.data"), index = Int(str) {
oldIndexes.append(index)
}
}
var oldIndexOffset = 0
var newIndexOffset = 0
// For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
// You may want to move rows in your content array and then call `tableView.reloadData()` instead.
tableView.beginUpdates()
for oldIndex in oldIndexes {
if oldIndex < row {
tableView.moveRowAtIndex(oldIndex + oldIndexOffset, toIndex: row - 1)
--oldIndexOffset
} else {
tableView.moveRowAtIndex(oldIndex, toIndex: row + newIndexOffset)
++newIndexOffset
}
}
tableView.endUpdates()
return true
}
Swift 3version:
斯威夫特 3版本:
extension MyViewController: NSTableViewDelegate, NSTableViewDataSource {
// numerbOfRow and viewForTableColumn methods
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
let item = NSPasteboardItem()
item.setString(String(row), forType: self.dragDropType)
return item
}
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
if dropOperation == .above {
return .move
}
return []
}
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
var oldIndexes = [Int]()
info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) { dragItem, _, _ in
if let str = (dragItem.item as! NSPasteboardItem).string(forType: self.dragDropType), let index = Int(str) {
oldIndexes.append(index)
}
}
var oldIndexOffset = 0
var newIndexOffset = 0
// For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
// You may want to move rows in your content array and then call `tableView.reloadData()` instead.
tableView.beginUpdates()
for oldIndex in oldIndexes {
if oldIndex < row {
tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
oldIndexOffset -= 1
} else {
tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
newIndexOffset += 1
}
}
tableView.endUpdates()
return true
}
}
回答by Olympiloutre
@Ethan's solution - Update Swift 4
@Ethan 的解决方案 - 更新 Swift 4
in viewDidLoad
:
在viewDidLoad
:
func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {
let data = NSKeyedArchiver.archivedData(withRootObject: rowIndexes)
pboard.declareTypes(["SomeType"], owner: self)
pboard.setData(data, forType: "SomeType")
return true
}
Later on delegate extension :
后来委托扩展:
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int,
proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
if dropOperation == .above {
return .move
} else {
return []
}
}
Plus, for those it may concerne:
另外,对于那些可能关心的人:
If you want to disablecertain cells from being dragable, return
nil
inpasteboardWriterForRow
s methodIf you want to prevent drop a certain locations ( too far for instance ) just use
return []
invalidateDrop
's methodDo not call tableView.reloadData() synchronously inside
func tableView(_ tableView:, acceptDrop info:, row:, dropOperation:)
. This will disturb Drag and Drop animation, and can be very confusing. Find a way to wait until animation finishes, and async it's reloading
如果要禁用某些单元格的可拖动,请
nil
在pasteboardWriterForRow
s 方法中返回如果您想防止删除某个位置(例如太远),只需使用
return []
invalidateDrop
的方法不要在 内部同步调用 tableView.reloadData()
func tableView(_ tableView:, acceptDrop info:, row:, dropOperation:)
。这会干扰拖放动画,并且可能会非常混乱。找到一种方法来等待动画完成,然后异步重新加载
回答by tesla
This answer covers Swift 3, View-based NSTableView
s and single/multiple rows drag&drop reorder.
此答案涵盖Swift 3、基于视图的NSTableView
s 和单/多行拖放重新排序。
There are 2 main steps which must be performed in order to achieve this:
为了实现这一点,必须执行两个主要步骤:
Register table view to a specifically allowed type of object which can be dragged.
tableView.register(forDraggedTypes: ["SomeType"])
Implement 3
NSTableViewDataSource
methods:writeRowsWith
,validateDrop
andacceptDrop
.
将表格视图注册到可以拖动的特定允许类型的对象。
tableView.register(forDraggedTypes: ["SomeType"])
实现 3
NSTableViewDataSource
种方法:writeRowsWith
,validateDrop
和acceptDrop
.
Before drag operation has started, store IndexSet
with indexes of rows which will be dragged, in the pasteboard.
在拖动操作开始之前IndexSet
,将要拖动的行的索引存储在粘贴板中。
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
let pasteboard = info.draggingPasteboard()
let pasteboardData = pasteboard.data(forType: "SomeType")
if let pasteboardData = pasteboardData {
if let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: pasteboardData) as? IndexSet {
var oldIndexOffset = 0
var newIndexOffset = 0
for oldIndex in rowIndexes {
if oldIndex < row {
// Dont' forget to update model
tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
oldIndexOffset -= 1
} else {
// Dont' forget to update model
tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
newIndexOffset += 1
}
}
}
}
return true
}
Validate drop only if dragging operation is above of specified row. This ensures when dragging is performed other rows won't be highlighted when dragged row will float above them. Also, this fixes an AutoLayout
issue.
仅当拖动操作位于指定行上方时才验证放置。这确保在执行拖动时,当拖动的行将浮动在其他行上方时,其他行不会突出显示。此外,这解决了一个AutoLayout
问题。
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
let pasteboard = info.draggingPasteboard()
guard let pasteboardData = pasteboard.data(forType: basicTableViewDragAndDropDataType) else { return false }
guard let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: pasteboardData) as? IndexSet else { return false }
guard let oldIndex = rowIndexes.first else { return false }
let newIndex = oldIndex < row ? row - 1 : row
tableView.moveRow(at: oldIndex, to: newIndex)
// Dont' forget to update model
return true
}
When accepting drop just retrieve IndexSet
that previously was saved in the pasteboard,
iterate through it and move rows using calculated indexes.
Note: Part with iteration and row moving I've copied from @Ethan's answer.
当接受放置时,只需检索IndexSet
以前保存在粘贴板中的内容,遍历它并使用计算索引移动行。
注意:我从@Ethan 的回答中复制了迭代和行移动的部分。
let dragDropTypeId = "public.data" // or any other UTI you want/need
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
let item = NSPasteboardItem()
item.setString(String(row), forType: dragDropTypeId)
return item
}
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
if dropOperation == .above {
return .move
}
return []
}
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
var oldIndexes = [Int]()
info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
if let str = (##代码##.0.item as! NSPasteboardItem).string(forType: self.dragDropTypeId), let index = Int(str) {
oldIndexes.append(index)
}
}
var oldIndexOffset = 0
var newIndexOffset = 0
// For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
// You may want to move rows in your content array and then call `tableView.reloadData()` instead.
tableView.beginUpdates()
for oldIndex in oldIndexes {
if oldIndex < row {
tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
oldIndexOffset -= 1
} else {
tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
newIndexOffset += 1
}
}
tableView.endUpdates()
self.reloadDataIntoArrayController()
return true
}
View-based NSTableView
s update themselfs when moveRow
is called, there is no need to use beginUpdates()
and endUpdates()
block.
View-based NSTableView
s 在moveRow
被调用时更新自己,不需要使用beginUpdates()
和endUpdates()
阻塞。
回答by Besi
Unfortunately you do have to write the Paste board code. The Drag and Drop API is fairly generic which makes it very flexible. However, if you just need reordering it's a bit over-the-top IMHO. But anyway, I have created a small sample projectwhich has an NSOutlineView
where you can add and remove items as well as reorder them.
不幸的是,您必须编写粘贴板代码。拖放 API 是相当通用的,这使得它非常灵活。但是,如果您只需要重新排序,恕我直言就有点过头了。但无论如何,我已经创建了一个小样本项目它有一个NSOutlineView
在这里你可以添加和删除项目,以及重新排序。
This is not an NSTableView
but the implementation of the Drag & Drop protocol is basically identical.
这不是一个NSTableView
但拖放协议的实现基本上是相同的。
I implemented drag and Drop in one go so it's best to look at this commit.
我一次性实现了拖放,所以最好看看这个提交。
回答by Tiago Martinho
If your are moving only one row at the time you can use the following code:
如果您一次只移动一行,您可以使用以下代码:
##代码##回答by Ben Leggiero
This is an update to @Ethan's answerfor Swift 3:
##代码##