Swift：2つのNSTableView間でアイテムをドラッグ＆ドロップ

# Demo

![table.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67153/6badf261-91a5-947d-95ca-5251c94cee11.gif)

# 下準備

対象となる2つのTableViewのそれぞれにおいて、`TableColumn`と`TableCellView`のIdentifierに同じキーワードを指定しておく。（テーブルのアイテムを追加するための措置）

![group.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67153/3356fbfb-a5d3-62d4-1dfb-23fbec5dba66.png)

# ソース

```swift
import Cocoa

class ViewController: NSViewController {

    @IBOutlet weak var tableViewA: NSTableView!
    @IBOutlet weak var tableViewB: NSTableView!

    let registeredType = NSPasteboard.PasteboardType.string
    var groupA: [String] = ["Apple", "Banana", "Grape", "Peach"]
    var groupB: [String] = ["Gomi", "Hoge", "Piyo"]

    override func viewDidLoad() {
        super.viewDidLoad()

        tableViewA.delegate = self
        tableViewB.delegate = self
        tableViewA.dataSource = self
        tableViewB.dataSource = self

        // ドラッグ & ドロップのために必要
        tableViewA.registerForDraggedTypes([registeredType])
        tableViewB.registerForDraggedTypes([registeredType])

        tableViewA.reloadData()
        tableViewB.reloadData()
    }

}

extension ViewController: NSTableViewDelegate, NSTableViewDataSource {

    func numberOfRows(in tableView: NSTableView) -> Int {
        // それぞれのテーブルのアイテム数を返す
        if tableView === tableViewA {
            return groupA.count
        } else if tableView === tableViewB {
            return groupB.count
        }
        return 0
    }

    func tableView(_ tableView: NSTableView,
                   viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let cell = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? NSTableCellView
        if tableView === tableViewA {
            cell?.textField?.stringValue = groupA[row]
        } else if tableView === tableViewB {
            cell?.textField?.stringValue = groupB[row]
        }
        return cell
    }

    // drag & drop
    func tableView(_ tableView: NSTableView,
                   validateDrop info: NSDraggingInfo,
                   proposedRow row: Int,
                   proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
        if dropOperation == .above {
            return NSDragOperation.move
        }
        return .every
    }

    func tableView(_ tableView: NSTableView,
                   writeRowsWith rowIndexes: IndexSet,
                   to pboard: NSPasteboard) -> Bool {
        guard tableView === tableViewA || tableView === tableViewB else {
            return false
        }
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: rowIndexes,
                                                        requiringSecureCoding: false)
            pboard.declareTypes([registeredType], owner: self)
            pboard.setData(data, forType: registeredType)
            return true
        } catch {
            Swift.print(error.localizedDescription)
        }
        return false
    }

    func tableView(_ tableView: NSTableView,
                   acceptDrop info: NSDraggingInfo,
                   row: Int,
                   dropOperation: NSTableView.DropOperation) -> Bool {
        let pboard = info.draggingPasteboard
        guard
            let data = pboard.data(forType: registeredType),
            let rowIndexes = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? IndexSet,
            let source = info.draggingSource as? NSTableView,
            let sourceRow = rowIndexes.min()
            else { return false }
        let targetIndexes = rowIndexes.sorted()
        var tmp = [String]()
        let beforeCount = targetIndexes.filter({ (n) -> Bool in
            return n < row
        }).count
        if source == tableViewA && tableView == tableViewB {
            let value = groupA[sourceRow]
            groupA.remove(at: sourceRow)
            groupB.append(value)
        }
        if source == tableViewB && tableView == tableViewA {
            let value = groupB[sourceRow]
            groupB.remove(at: sourceRow)
            groupA.append(value)
        }
        tableViewA.reloadData()
        tableViewB.reloadData()
        return true
    }

}
```

![kyome](https://img.shields.io/badge/kyome-2.2.2-green)