Macアプリ初心者：NSSearchField について調べてみる


今回は NSSearchField について調べてみました。
とりあえず、[前に作成したプロジェクト](https://qiita.com/ynozue/items/da96d8bf1a2e2e1f3da0)に検索機能を追加してみます。

# 完成イメージ
[![完成イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/d27eab17-7d33-803e-0ec6-8b8980dd3837.png)](https://www.youtube.com/watch?v=retMss4xmIg&feature=youtu.be)


# 環境

macOS Mojave：10.14.6
Xcode：10.3

# StoryBoard で NSSearchField を NSTableView の上部に配置
※ Auto Layout は適当に設定してます。
![Kobito.rs7vxc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/30fa1db5-a69b-0664-4f5d-4f530ff22c69.png "Kobito.rs7vxc.png")

# NSSearchField の Action を作成
![Kobito.3zQE3q.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/480c24b2-e9b9-8736-d05e-9ef2a6fe9d7c.png "Kobito.3zQE3q.png")

## アクションの挙動について
※ アクション実行のタイミングを設定することができる。
<img width="350px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/ea40fb52-6fa6-4a5d-de08-a952b3ca941b.png">
<img width="350px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/ae91f1fc-cd09-7598-1031-356548b6329a.png">

* Send On Enter Only：Enter ボタン押した場合だけアクションが実行される（デフォルト）
* Sent On End Editting：編集が終わったタイミングでアクションが実行される

# ソースコード
## 検索/表示用の変数を用意
```swift:ViewController.swift
    private var originalValues = [
        ["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"]
    ]

    private lazy var values = self.originalValues
```

## 検索処理を作成
※ 表示する際にどのアイテムが追加/削除されたか計算が面倒臭いので `reloadData` で全アイテムを再描画

```swift:ViewController.swift
    
    // MARK: - actions
    
    @IBAction func actionSearchField(_ sender: NSSearchField) {
        print(sender)
        
        let searchValue = sender.stringValue.trimmingCharacters(in: .whitespaces)
        if searchValue != "" {
            var tmpValues = [[String:Any]]()
            for value in self.originalValues {
                if (value["name"] as! String).contains(searchValue) {
                    tmpValues.append(value)
                }
            }
            self.values = tmpValues
            self.tableView.reloadData()
        } else {
            self.values = self.originalValues
            self.tableView.reloadData()
        }
    }

```



<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.headerView = nil
            self.tableView.rowHeight = 88
        }
    }
    
    // MARK: - private variables
    private var originalValues = [
        ["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"]
    ]
    
    private lazy var values = self.originalValues


    // 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.
        }
    }
    
    // MARK: - actions
    
    @IBAction func actionSearchField(_ sender: NSSearchField) {
        print(sender)
        
        let searchValue = sender.stringValue.trimmingCharacters(in: .whitespaces)
        if searchValue != "" {
            var tmpValues = [[String:Any]]()
            for value in self.originalValues {
                if (value["name"] as! String).contains(searchValue) {
                    tmpValues.append(value)
                }
            }
            self.values = tmpValues
            self.tableView.reloadData()
        } else {
            self.values = self.originalValues
            self.tableView.reloadData()
        }
    }
    
}

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>

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