Table of Contents
複数の要素を並べて表示する NSTableView の Swift によるプログラム的な作成方法と、各行の要素をドラッグ&ドロップで並び替え可能にするためのコードについてです。
以下のような、テキストのみの要素を一列に表示する簡単な NSTableView のサンプルを作ります。
サンプルのソースコード
Cocoa Application のプロジェクトを新規作成し、生成される ViewController.swift を以下のように変更します。
import Cocoa
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
// 表示する要素
var tableViewData: [String] = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
// セルのUTI
let DRAG_TYPE = "public.data"
override func viewDidLoad() {
super.viewDidLoad()
let tableViewWidth: CGFloat = 400
let tableView = NSTableView(frame: NSRect(x: 0, y: 0, width: tableViewWidth, height: 400))
tableView.dataSource = self
tableView.delegate = self
tableView.registerForDraggedTypes([NSPasteboard.PasteboardType(DRAG_TYPE)])
// 列の設定
let tableColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("column"))
tableColumn.width = tableViewWidth
tableView.addTableColumn(tableColumn)
// NSTableView を NSClipView に設定
let scrollContentView = NSClipView(frame: NSRect(x: 0, y: 0, width: 400, height: 400))
scrollContentView.documentView = tableView
// NSClipView を NSScrollView に設定
let scrollView = NSScrollView(frame: NSRect(x: 50, y: 50, width: 400, height: 400))
scrollView.contentView = scrollContentView
self.view.addSubview(scrollView)
}
// 行数
func numberOfRows(in tableView: NSTableView) -> Int {
return tableViewData.count
}
// 各行の要素(セル)
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return NSCell(textCell: tableViewData[row])
}
// 行の高さ
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
return 40
}
// 以下ドラッグ&ドロップのための設定
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 {
// IndexSet の情報を NSPasteboard に保持させる
let data = NSKeyedArchiver.archivedData(withRootObject: rowIndexes)
pboard.declareTypes([NSPasteboard.PasteboardType(DRAG_TYPE)], owner: self)
pboard.setData(data, forType: NSPasteboard.PasteboardType(DRAG_TYPE))
return true
}
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
// NSPasteboard から行の情報を取り出す
let pasteboard = info.draggingPasteboard()
let pasteboardData = pasteboard.data(forType: NSPasteboard.PasteboardType(DRAG_TYPE))
if let pasteboardData = pasteboardData {
if let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: pasteboardData) as? IndexSet {
tableView.beginUpdates()
for oldIndex in rowIndexes {
print(oldIndex) // 元の位置
print(row) // 移動後の位置
if oldIndex < row {
// 要素を下に移動させる場合
tableView.moveRow(at: oldIndex, to: row - 1)
let oldElm = tableViewData[oldIndex]
tableViewData.remove(at: oldIndex)
tableViewData.insert(oldElm, at: row - 1)
} else {
// 要素を上に移動させる場合
if row < tableViewData.count {
tableView.moveRow(at: oldIndex, to: row)
let oldElm = tableViewData[oldIndex]
tableViewData.remove(at: oldIndex)
tableViewData.insert(oldElm, at: row)
}
}
}
tableView.endUpdates()
return true
}
print("failed")
}
return false
}
}
viewDidLoad における処理
NSTableView は単独ではなく NSScrollView および NSClipView と共に動作します。また、1つの NSTableView には複数の列を表示できるため、表示する列の数だけ NSTableColumn を追加します。
今回のサンプルでは一列のみ表示させるため NSTableColumn はひとつだけ追加しています。
基本動作やドラッグ&ドロップによる並べ替え機能を実装するためには NSTableViewDataSource および NSTableViewDelegate の2つのプロトコルが必要です。
ドラッグ&ドロップ可能なデータタイプ(UTI)は registerForDraggedTypes で設定します。このサンプルでは public.data です。
ドラッグ&ドロップによる並び替え処理
並び替えの処理には、以下のメソッドを使用します
- tableView(_:validateDrop:proposedRow:proposedDropOperation:)
- tableView(_:writeRowsWith:to:)
- tableView(_:acceptDrop:row:dropOperation:)
ドロップしたときに起こる実際の並び替え処理は tableView(_:acceptDrop:row:dropOperation:) における beginUpdates と endUpdates の間で行われます。
...
for oldIndex in rowIndexes {
print(oldIndex) // 元の位置
print(row) // 移動後の位置
if oldIndex < row {
tableView.moveRow(at: oldIndex, to: row - 1)
let oldElm = tableViewData[oldIndex]
tableViewData.remove(at: oldIndex)
tableViewData.insert(oldElm, at: row - 1)
} else {
if row < tableViewData.count {
tableView.moveRow(at: oldIndex, to: row)
let oldElm = tableViewData[oldIndex]
tableViewData.remove(at: oldIndex)
tableViewData.insert(oldElm, at: row)
}
}
}
...
moveRow は、ある番号の要素を指定した場所に移動するアニメーションを行うメソッドです。あくまで移動アニメーションを行うだけであり、実際の要素の並び替えが起こるわけではないので、その処理は別に書く必要があります。
このサンプルでは、実際の配列の要素を削除(remove)と挿入(insert)によって移動させています。要素の移動方向によって挙動が異なるため、場合分けを行っています。
以上、要素の並び替えができる NSTableView についてでした。


大変参考になりました。
Macのプログラミング資料は本当に貴重です。
最近は書籍もほとんどないし、Appleもかみ砕いた資料はほとんど出さなくなったし。