Macアプリ初心者：NSTableView でカスタムした一覧を作ってみる


今回は[前回作成したプロジェクト](https://qiita.com/ynozue/items/0fe35afd8eb7cd4a7f0f)を元にしてカスタム Cell を作成して見た目を変えていきます。
株価一覧を表示するイメージのサンプルアプリを作ってみます。

# 完成イメージ
こんな感じのシンプルなものです。
![スクリーンショット 2019-08-28 9.22.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/a3669350-c7f3-5212-f51d-f25824b70c7e.png "スクリーンショット 2019-08-28 9.22.05.png")

# 環境
* macOS Mojave：10.14.6
* Xcode：10.3

# カスタムセルの作成
今回は StoryBoard を利用してカスタムセルを作成しました。
個人的には大きいプロジェクトでは XIB を利用して個々のパーツに分解して作った方がメンテしやすいと思います。

## カスタムセルのイメージ
株価のステータス表示用の ImageView が左側に配置されていて、右側に企業名と株価を表示する TextField を配置するシンプルな構成です。
![スクリーンショット 2019-08-28 9.35.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/d5ff5411-c7e7-cef1-76b4-763efafbe527.png "スクリーンショット 2019-08-28 9.35.15.png")

## StoryBoard でアイテムを配置
![スクリーンショット 2019-08-28 9.38.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/407190b4-7fb1-d5d0-11ee-afa77d82dab0.png "スクリーンショット 2019-08-28 9.38.37.png")

## TableView の見え方を変更
表示するだけのViewのため、Clickした時に選択できないようにし（Highlight：None）、見やすいように縞模様表記にし（Alternating Rows：チェック）、水平のグリッド線を表示（Horizontal Grid：Solid）します。
<img width="350px" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/a4b3ae7e-5ad7-cdf7-3e50-ed758ff404bb.png">

## カスタムセルクラスを作成
それぞれのアイテムを持っているだけの単純なクラスです。

```swift:CustomNSTableCellView.swift
import Cocoa

class CustomNSTableCellView: NSTableCellView {

    @IBOutlet weak var stockImage: NSImageView!
    @IBOutlet weak var companyName: NSTextField!
    @IBOutlet weak var stockPrice: NSTextField!
    
}
```

## StoryBoard とカスタムセルクラスを紐づける
![Kobito.SX5sr9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/0d43f9d6-a4dc-d546-8346-d54306b36533.png "Kobito.SX5sr9.png")


# ソースコード：カスタムセルクラスの利用
## 下準備としてセルの高さと表示するアイテムデータを作成
```swift:ViewController.swift

    @IBOutlet weak var tableView: NSTableView! {
        didSet {
            self.tableView.dataSource = self
            self.tableView.delegate = self
            
            self.tableView.rowHeight = 88
        }
    }
    
    // MARK: - private variables
    private var values = [
        ["name" : "○○株式会社", "price": 1000, "status": "up"]
        ,["name" : "○×水産", "price": 12345, "status": "down"]
        ,["name" : "株式会社□○", "price": 345, "status": "flat"]
        ,["name" : "×△ホールディングス", "price": 321, "status": "up"]
        ,["name" : "ABC BANK", "price": 20, "status": "down"]
        ,["name" : "▼○重工", "price": 98000, "status": "up"]
    ]
```

## 状態によってテキストやイメージ画像を差し替える
```swift:CustomNSTableCellView.swift
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let result = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "MyView"), owner: self) as? CustomNSTableCellView
        
        // Set the stringValue of the cell's text field to the nameArray value at row
        switch self.values[row]["status"] as! String {
        case "up":
            result?.stockImage.image = NSImage(named: NSImage.Name("up"))
            result?.stockImage.contentTintColor = NSColor.red
            result?.companyName.textColor = NSColor.red
        case "down":
            result?.stockImage.image = NSImage(named: NSImage.Name("down"))
            result?.stockImage.contentTintColor = NSColor.green
            result?.companyName.textColor = NSColor.green
        case "flat":
            result?.stockImage.image = NSImage(named: NSImage.Name("flat"))
            result?.stockImage.contentTintColor = NSColor.gray
            result?.companyName.textColor = NSColor.gray
        default:
            break
        }
        result?.companyName.stringValue = self.values[row]["name"] as! String
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        let commaString = formatter.string(from: self.values[row]["price"] as! NSNumber)
        result?.stockPrice.stringValue = commaString ?? "-"

        // Return the result
        return result
    }
```


<details><summary>全ソースコード(ViewController)</summary><div>

```swift:ViewController.swift
import Cocoa

class ViewController: NSViewController {

    // MARK: - IBOutlet
    @IBOutlet weak var tableView: NSTableView! {
        didSet {
            self.tableView.dataSource = self
            self.tableView.delegate = self
            
            self.tableView.rowHeight = 88
        }
    }
    
    // MARK: - private variables
    private var values = [
        ["name" : "○○株式会社", "price": 1000, "status": "up"]
        ,["name" : "○×水産", "price": 12345, "status": "down"]
        ,["name" : "株式会社□○", "price": 345, "status": "flat"]
        ,["name" : "×△ホールディングス", "price": 321, "status": "up"]
        ,["name" : "ABC BANK", "price": 20, "status": "down"]
        ,["name" : "▼○重工", "price": 98000, "status": "up"]
    ]


    // MARK: - override func
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

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

extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
    
    // MARK: - NSTableViewDataSource
    func numberOfRows(in tableView: NSTableView) -> Int {
        return self.values.count
    }
    
    // MARK: - NSTableViewDelegate
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let result = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "MyView"), owner: self) as? CustomNSTableCellView
        
        // Set the stringValue of the cell's text field to the nameArray value at row
        switch self.values[row]["status"] as! String {
        case "up":
            result?.stockImage.image = NSImage(named: NSImage.Name("up"))
            result?.stockImage.contentTintColor = NSColor.red
            result?.companyName.textColor = NSColor.red
        case "down":
            result?.stockImage.image = NSImage(named: NSImage.Name("down"))
            result?.stockImage.contentTintColor = NSColor.green
            result?.companyName.textColor = NSColor.green
        case "flat":
            result?.stockImage.image = NSImage(named: NSImage.Name("flat"))
            result?.stockImage.contentTintColor = NSColor.gray
            result?.companyName.textColor = NSColor.gray
        default:
            break
        }
        result?.companyName.stringValue = self.values[row]["name"] as! String
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        let commaString = formatter.string(from: self.values[row]["price"] as! NSNumber)
        result?.stockPrice.stringValue = commaString ?? "-"

        // Return the result
        return result
    }
}
```

</div></details>

よかったら **いいね** してください。