NSTableView(view-based)のセルをカスタムセルを使ってカスタマイズする方法を説明します。

NSTableViewのデフォルトのセルで表示できない情報をテーブルに表示したい場合、独自のxibファイルを持つビューをカスタマイズし、それをセルとして利用することができます。

ここでは1つのセルの中に本の情報(タイトルと出版社)を表示する、簡単なプログラムを作成します。

以下具体的な手順を説明します。

プロジェクトの作成

macOSアプリケーションを作成します。Xcodeを起動しメニューから「File > New > Project」を選択します。

作成するアプリの種類は「macOS」の中にある「App」です。Interfaceでは「Storyboard」を、Languageは「Swift」を選びます。

▲Main.storyboardを選択しTable Viewをドロップします。オートレイアウトを利用してレイアウトした後、アシスタントエディタを開き、Ctrl+ドラッグしてtableViewという名前をつけます。

▲テーブルの列の数を1に変更します。Interface BuilderでTable Viewを選択し、Attributes InspectorでColumnsを「1」に変更します。

ViewController.swiftのソースコードを編集します。本情報(Books)の内容を各行に表示するシンプルな内容です。

import Cocoa


struct Book {
    var title: String
    var publisher: String
}

class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {

    @IBOutlet weak var tableView: NSTableView!
    
    var books = [Book]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        books.append(Book(title:"できるAppKit", publisher: "海空出版"))
        books.append(Book(title:"わかるSwiftUI", publisher: "テック出版"))
        books.append(Book(title:"わかるUIKit", publisher: "テック出版"))
        tableView.register(NSNib(nibNamed: "CustomCell", bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: "myCell"))
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

    // MARK: - NSTableViewDataSource
    func numberOfRows(in tableView: NSTableView) -> Int {
        return books.count
    }

    // MARK: - NSTableViewDelegate
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        
        guard let identifier = tableColumn?.identifier else {
            return nil
        }
        guard let cell = tableView.makeView(withIdentifier: identifier, owner: self) as? NSTableCellView else {
            return nil
        }
        let book = books[row]
        cell.textField?.stringValue = book.title
        return cell
    }
}

一度実行してみます。

▲現段階では本のタイトルだけが表示されます

カスタムセルの作成

次にカスタムセルを作成します。

▲「File > New > File」でmacOSタブの中にある「Cocoa Class」を選択します。

▲「Class」を「CuctomCell」に、「Subclass of」を「NSTableCellView」にします。「Also create XIB file」はなぜかグレーアウトしていていチェックを入れることはできませんので後で別に作成します。

XIBファイルを作成します。

▲「File > New > File」でmacOSタブの中にある「View(User Iterface)」を選択します。名前を「CustomCell」として、CuxtomCell.xibを作成します。

▲CustomCell.xibを開き、コントロールライブラリからText Fieldを2つドロップします。オートレイアウトを利用して適切にレイアウトした後、アシスタントエディタを開き、Text FieldをソースコードにCtrl+ドラッグして名前をつけます。今回はleftField、rightFieldとしました。

CustomCell.swiftは次のようになります。

import Cocoa

class CustomCell: NSTableCellView {
    @IBOutlet weak var textLeft: NSTextField!
    
    @IBOutlet weak var textRight: NSTextField!
    
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        // Drawing code here.
    }
    
}

カスタムセルを使用する

ViewControllerを変更し、作成したCustomCellを使えるようにします。

tableView.registerでカスタムセル使えるようにし、tableView.makeViewでそのセルを取得します。

変更後のソースコードは次のようになります。

import Cocoa


struct Book {
    var title: String
    var publisher: String
}

class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {

    @IBOutlet weak var tableView: NSTableView!
    
    var books = [Book]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        books.append(Book(title:"できるAppKit", publisher: "海空出版"))
        books.append(Book(title:"わかるSwiftUI", publisher: "テック出版"))
        books.append(Book(title:"わかるUIKit", publisher: "テック出版"))
        tableView.register(NSNib(nibNamed: "CustomCell", bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: "myCell"))
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

    // MARK: - NSTableViewDataSource
    func numberOfRows(in tableView: NSTableView) -> Int {
        return books.count
    }

    // MARK: - NSTableViewDelegate
//    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
//        
//        guard let identifier = tableColumn?.identifier else {
//            return nil
//        }
//        guard let cell = tableView.makeView(withIdentifier: identifier, owner: self) as? NSTableCellView else {
//            return nil
//        }
//        let book = books[row]
//        cell.textField?.stringValue = book.title
//        return cell
//    }
    
    // MARK: - NSTableViewDelegate
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
    
        guard let identifier = tableColumn?.identifier else {
            return nil
        }
        guard let cell = tableView.makeView(withIdentifier: identifier, owner: self) as? NSTableCellView else {
            return nil
        }
        let book = books[row]
        if identifier == NSUserInterfaceItemIdentifier("title") {
            cell.textField?.stringValue = book.title
        } else if identifier == NSUserInterfaceItemIdentifier("publisher") {
            cell.textField?.stringValue = book.publisher
        }
        return cell
    }
}

実行します。

▲1つの列の中に本のタイトルと出版社が表示されました。

まとめ

NSTableViewのセルをカスタムセルを用いてカスタマイズする方法を説明しました。テキストフィールド以外にも自由にコントロールを配置することができます。