LoginSignup
5
4

More than 3 years have passed since last update.

NSOutlineViewの基本的な実装

Last updated at Posted at 2021-02-20

概要

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

image

GitHub

参考

実装

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の設定

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を使うことで煩雑な処理、例えば複数選択のアクション・入れ替え・削除が簡単に扱えると思っている。

参考

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4