macos drag-and-drop nstableview

macos - Arrastrar y soltar Reordenar filas en NSTableView



drag-and-drop (6)

Me preguntaba si había una manera fácil de configurar NSTableView para permitirle reordenar sus filas sin escribir ningún código de cartón. Solo necesito que sea capaz de hacer esto internamente, dentro de una sola tabla. No tengo ningún problema para escribir el código del tablero, salvo que estoy bastante seguro de que vi que Interface Builder tenía un botón para este en algún lugar / lo vi funcionar de manera predeterminada. Ciertamente parece una tarea bastante común.

Gracias


Lamentablemente, debe escribir el código de la placa Pegar. La API Drag and Drop es bastante genérica, lo que la hace muy flexible. Sin embargo, si solo necesitas reordenar, es un poco exagerado en mi humilde opinión. Pero de todos modos, he creado un pequeño proyecto de ejemplo que tiene un NSOutlineView donde puede agregar y eliminar elementos, así como reordenarlos.

Esta no es una NSTableView pero la implementación del protocolo Drag & Drop es básicamente idéntica.

Implementé arrastrar y soltar de una vez, por lo que es mejor mirar este compromiso .


Esta respuesta cubre Swift 3 , NSTableView basado en la NSTableView y filas individuales / múltiples arrastrar y soltar reordenar.

Hay dos pasos principales que deben realizarse para lograr esto:

  • Registre la vista de tabla en un tipo de objeto específicamente permitido que pueda arrastrarse.

    tableView.register(forDraggedTypes: ["SomeType"])

  • Implementar 3 métodos NSTableViewDataSource : writeRowsWith , validateDrop y acceptDrop .

Antes de que la operación de arrastre haya comenzado, almacene IndexSet con índices de filas que serán arrastrados, en la mesa de trabajo.

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 }

Validar drop solo si la operación de arrastrar está arriba de la fila especificada. Esto asegura que cuando se realiza el arrastre, otras filas no se resaltarán cuando la fila arrastrada flote sobre ellas. Además, esto soluciona un problema de AutoLayout .

func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation { if dropOperation == .above { return .move } else { return [] } }

Cuando acepte descartar solo recupere IndexSet que previamente se guardó en la mesa de trabajo, repítalo y mueva las filas usando los índices calculados. Nota: Parte con iteración y movimiento de fila he copiado de la respuesta de @ Ethan.

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 }

Las NSTableView basadas en la NSTableView actualizan cuando se llama a moveRow , no es necesario usar el beginUpdates() y endUpdates() .


Si solo está moviendo una fila en el momento, puede usar el siguiente código:

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 }


Establezca el origen de datos de su vista de tabla para que sea una clase que se ajuste a NSTableViewDataSource .

Coloque esto en un lugar apropiado ( -awakeFromNib , -viewDidLoad , -viewDidLoad o algo similar):

tableView.registerForDraggedTypes(["public.data"])

A continuación, implemente estos tres métodos NSTableViewDataSource :

tableView:pasteboardWriterForRow: tableView:validateDrop:proposedRow:proposedDropOperation: tableView:acceptDrop:row:dropOperation:

Aquí está el código completamente funcional que admite la función de "arrastrar y soltar" reordenando varias filas:

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 = ($0.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 3 versión:

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 = ($0.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 }


Esta es una actualización de la respuesta de @ Ethan para Swift 3:

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.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 }


Si echas un vistazo a la información sobre herramientas en IB verás que la opción a la que te refieres

- (BOOL)allowsColumnReordering

controles, bueno, reordenamiento de columna. No creo que haya otra manera de hacer esto que no sea la API estándar de arrastrar y colocar para las vistas de tabla .

EDITAR: (2012-11-25)

La respuesta se refiere al reordenamiento de arrastrar y colocar de NSTableViewColumns ; y mientras era la respuesta aceptada en ese momento. No parece, ahora casi 3 años después, estar en lo correcto. En el servicio de hacer que la información sea útil para los buscadores, intentaré dar la respuesta más correcta.

No hay ninguna configuración que permita arrastrar y soltar el reordenamiento de NSTableView filas de NSTableView en el Interface Builder. NSTableViewDataSource implementar ciertos métodos NSTableViewDataSource , que incluyen:

- 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

Hay otras preguntas SO que abordan esto razonablemente exhaustivamente, incluido este