概要
-
NSOutlineView
を使って下記のように表示するだけの実装を行う。

GitHub
参考
-
NSOutlineView on macOS Tutorial
- 主に参考とした
- NSOutlineView 開閉時の挙動を比較してみる - Qiita
- HIG: Outline Views
- 公式ドキュメント: NSOutlineView
実装
Model
- 今回セルに対応されるModelは以下の通り。
-
NodeType
はそのセルがGroup
なのかを判断するのに使用している
enum NodeType {
case group
case parent
case item
}
class Node {
// MARK: - Properties
var title: String
var nodeType: NodeType
var children: [Node]
// MARK: - Lifecycle
init(title: String,
children: [Node] = [],
nodeType: NodeType = .item) {
self.title = title
self.children = children
self.nodeType = nodeType
}
// MARK: - Helpers
var numberOfChildren: Int {
children.count
}
var hasChildren: Bool {
!children.isEmpty
}
}
ViewController.swift
NSOutlineViewの設定
- 下記の通り
NSOutlineView
に必要な設定をコード内で行う -
selectionHighlightStyle
やfloatsGroupRows
の設定で見た目と挙動が大きく変わるので、下記を参考に必要なものを選択すると良い
private func initializeOutlineView() {
// 別のXibファイルから読み込む場合は登録が必要
outlineView.register(MyCellView.nib(inBundle: nil),
forIdentifier: NSUserInterfaceItemIdentifier(rawValue: String(describing: MyCellView.self)))
outlineView.delegate = self
outlineView.dataSource = self
outlineView.autosaveExpandedItems = true
// スタイルの設定
outlineView.selectionHighlightStyle = .regular
outlineView.floatsGroupRows = false
nodes.append(contentsOf: Node.createSampleNodes())
outlineView.reloadData()
}
Starting in OS X version 10.5, passing 'nil' will expand each item under the root in the outline view.
outlineView.expandItem(nil, expandChildren: true)
NSOutlineViewDataSource
-
NSOutlineView
のデータに関するDelegateメソッドを実装する - itemが含むchildrenの数を返す
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let node = item as? Node {
return node.numberOfChildren
}
return nodes.count
}
- 親に対する子の情報を与える
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let feed = item as? Node {
return feed.children[index]
}
return nodes[index]
}
- itemが開閉可能か(trueを返すとCellViewにDisclosure Buttonが表示される)
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let node = item as? Node {
return node.hasChildren
}
return false
}
- itemがグループ行のものかどうか
// グループ行かどうか(NSOutlineViewの設定如何では見た目と挙動が特別となる)
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
guard let node = item as? Node else {
return false
}
return node.nodeType == .group
}
NSOutlineViewDelegate
-
NSOutlineView
のDelegateメソッドを実装する - 各行に表示するCellViewの設定
public func outlineView(_ outlineView: NSOutlineView,
viewFor tableColumn: NSTableColumn?,
item: Any) -> NSView? {
guard
let myCellView = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: String(describing: MyCellView.self)), owner: self) as? MyCellView,
let node = item as? Node
else {
return nil
}
myCellView.configureUI(withNode: node)
return myCellView
}
- 行選択が変更された場合に呼ばれるNotification
func outlineViewSelectionDidChange(_ notification: Notification) {
guard let outlineView = notification.object as? NSOutlineView else {
return
}
let selectedIndex = outlineView.selectedRow
guard let node = outlineView.item(atRow: selectedIndex) as? Node else {
return
}
print("Selected Title: \(node.title)")
}
NSTreeControllerの話
-
NSOutlineView
で調べているとNSTreeController
を使っている例が多くヒットする。 - ただ
CocoaBinding
が絡んでいて、私の理解が浅いためちょっと使いこなせなかった。- ので今回は
NSOutlineView
単体で実装している
- ので今回は
- イメージに過ぎないが、
NSTreeController
を使うことで煩雑な処理、例えば複数選択のアクション・入れ替え・削除が簡単に扱えると思っている。
参考
-
Navigating Hierarchical Data Using Outline and Split Views
- Appleのしっかりとした(珍しい)macOSのチュートリアル
- NSTreeController
-
The simplest NSTreeController example | Daemon Construction
- 何がなんだかわからんけど、なぜか動くサンプル
-
NSTreeController + NSOutlineView: A powerful combination | by Alexander Murphy | Building Ibotta | Medium
- コードベースのバインディングを行っていて比較的理解はしやすい
- NSTreeControllerを使うため、バインディングの基本を知りたいと思い…
-
How to Use Cocoa Bindings and Core Data in a Mac App
- CoreDataとバインディング。まだ見れていない。
-
NSTreeControllerで使用するObjectの変換についてのメモ — MindTo01s
- NSTreeControllerとNSOutlineviewの相互変換の話