Drag & Drop Reorder Rows on NSTableView

前端 未结 7 739
旧巷少年郎
旧巷少年郎 2020-12-08 11:21

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 thi

7条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2020-12-08 11:51

    This answer covers Swift 3, View-based NSTableViews and single/multiple rows drag&drop reorder.

    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 and acceptDrop.

    Before drag operation has started, store IndexSet with indexes of rows which will be dragged, in the pasteboard.

    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
        }
    

    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.

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

    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.

        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
        }
    

    View-based NSTableViews update themselfs when moveRow is called, there is no need to use beginUpdates() and endUpdates() block.

提交回复
热议问题