Drag & Drop Reorder Rows on NSTableView

前端 未结 7 741
旧巷少年郎
旧巷少年郎 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:58

    @Ethan's solution - Update Swift 4

    in viewDidLoad :

    private var dragDropType = NSPasteboard.PasteboardType(rawValue: "private.table-row")
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        myTableView.delegate = self
        myTableView.dataSource = self
        myTableView.registerForDraggedTypes([dragDropType])
    }
    

    Later on delegate extension :

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

    Plus, for those it may concerne:

    1. If you want to disable certain cells from being dragable, return nil in pasteboardWriterForRows method

    2. If you want to prevent drop a certain locations ( too far for instance ) just use return [] in validateDrop's method

    3. Do 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

提交回复
热议问题