Swift：NSTableViewの項目をドラッグで入れ替えできるようにする

# 概要
`TableView`の項目をドラッグで入れ替えられるようにしようと思ったら、MacかつMojave以降だとかなり厄介かつTipsが全然ないことに気づきました。忘備録！

# ソース
```swift:下準備
class ViewController: NSViewController {

    @IBOutlet weak var myTableView: NSTableView!
    @IBOutlet weak var segmentedControl: NSSegmentedControl!
    
    private var data = [String]()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        myTableView.delegate = self
        myTableView.dataSource = self
        
        // ↓これがドラッグを可能にする
        myTableView.registerForDraggedTypes([NSPasteboard.PasteboardType("public.data")])
        
        data.append("Apple")
        data.append("Banana")
        data.append("Grape")
        data.append("Peach")
        myTableView.reloadData()
        
        segmentedControl.setEnabled(false, forSegment: 0)
    }
    
    @IBAction func removeData(_ sender: Any) {
        data.remove(at: myTableView.selectedRow)
        myTableView.reloadData()
        segmentedControl.setEnabled(false, forSegment: 0)
    }
    
}
```


```swift:本実装
extension ViewController: NSTableViewDelegate, NSTableViewDataSource {
   
    func numberOfRows(in tableView: NSTableView) -> Int {
        return data.count
    }
    
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        // ここのIdentifierの指定の仕方要注意　StoryBoardでNSTableCellViewに対して設定する必要があります。
        let cell = myTableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"), owner: self) as? NSTableCellView
        cell?.textField?.stringValue = data[row]
        return cell
    }
    
    func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
        segmentedControl.setEnabled(true, forSegment: 0)
        return true
    }
    
    func tableViewSelectionDidChange(_ notification: Notification) {
        for i in (0 ..< data.count) {
            if myTableView.isRowSelected(i) {
                return
            }
        }
        segmentedControl.setEnabled(false, forSegment: 0)
    }
    
    // ここから下がドラッグ機能に必要
    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, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {
        do {
            // Mojaveから扱いが難しくなった部分，Tipsほぼ０
            let data = try NSKeyedArchiver.archivedData(withRootObject: rowIndexes, requiringSecureCoding: false)
            pboard.declareTypes([NSPasteboard.PasteboardType("public.data")], owner: self)
            pboard.setData(data, forType: NSPasteboard.PasteboardType("public.data"))
        } catch {
            Swift.print(error)
        }
        return true
    }
    
    func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
        let pboard = info.draggingPasteboard
        if let pboardData = pboard.data(forType: NSPasteboard.PasteboardType("public.data")) {
            do {
                if let rowIndexes = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(pboardData) as? IndexSet {
                    myTableView.beginUpdates()
                    for oldRow in rowIndexes {
                        if oldRow < row {
                            myTableView.moveRow(at: oldRow, to: row - 1)
                            data.swapAt(oldRow, row - 1)
                        } else if row < data.count {
                            myTableView.moveRow(at: oldRow, to: row)
                            data.swapAt(oldRow, row)
                        }
                    }
                    myTableView.endUpdates()
                    return true
                }
            } catch {
                Swift.print(error)
            }
        }
        return false
    }
    
}
```