Swift4 TableViw の実装 (...を自分のためだけに整理)

###Swift4 TableViw の実装を自分のためだけに整理

Swift4 ... インターネットでググりながら試行錯誤して来た結果...
自分なりの TableView の実装の形ができて来たので、自分のためだけに整理してみました。

最近作る TableView はこんな感じです。
セルの数が固定で、一部のセルをボタンとして機能させます。
データ数が変化するデータの表示には向かない内容の記事です...。 ^^;

![swift4_tableview_sample_3.gif](https://qiita-image-store.s3.amazonaws.com/0/9142/79feda84-c8d8-775a-0544-c7df5b3c26cd.gif)




###XIBでUIの設定

自分は UITableViewController は好まないので、 ViewController に　UITableView を貼り付けます。

UITableView の　Outlet設定を 図のように 3箇所 設定しています。
![swift4_tableview_set_1.png](https://qiita-image-store.s3.amazonaws.com/0/9142/486e869d-b37c-08fa-cf19-b13a0d534ef0.png)


また、 Style は Grouped　を好んで選択しています。
![swift4_tableview_set_2.png](https://qiita-image-store.s3.amazonaws.com/0/9142/a9e0c05d-52a9-957b-8154-196f5ac980fa.png)


###コーディング
あとは ViewController に　TableView を実装します。

####セルのタイトル
固定のセルのTableView　を作成する機会が多いので、最近はセル名を固定で定義し、 IndexPath から名称を取得できるようにしています。

```swift:ViewController.swift
extension ViewController {
    
    internal struct TableDef {
        
        //~~~ 各セクション ~~~~
        //項目名: セクション1
        static let Section1Rows = [
            "cell A",
            "cell B",
            "cell C",
            ]
        
        enum Section1RowIndex: Int, Codable {
            case
            cell_A = 0,
            cell_B = 1,
            cell_C = 2
        }
        // ...
        
        //~~~ セクション (以下の３つの配列の順序を合わせる) ~~~
        //セクション名
        static let Section = [
            "section 0",
            "section 1",
            "section 2",
        ]
        
        enum SectionRowIndex: Int, Codable {
            case
            section0 = 0,
            section1 = 1,
            section2 = 2
        }
        
        //項目名 (各セクションをまとめる)
        static let Rows = [
            TableDef.Section1Rows,
            TableDef.Section1Rows,
            TableDef.Section1Rows,
        ]
        
        
        //項目名を返す
        static func row(_ indexPath: IndexPath) -> String? {
            guard (0 <= indexPath.section), (indexPath.section < Rows.count) else {
                return nil
            }
            
            let lists = Rows[indexPath.section]
            
            guard (0 <= indexPath.row), (indexPath.row < lists.count) else {
                return nil
            }
            
            //正常終了
            return lists[indexPath.row]
        }
    }
}
```

#### KVO (Key Value Observer)
最近よく実装する形は、最近覚えた [KVO](https://qiita.com/BlueEventHorizon/items/bf37428b54b937728dc7) を利用し、変化がある時は TableView の該当するセルの中を更新しています。
(自分の場合は、セクションごと更新しています。)

```swift:ViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()

        self.initKVO()
    }

    fileprivate func initKVO() {
        
        //KVO: battery
        if let status = xxx.status {

            let kvo = status.observe(\.xxx) { (status, change) in
                //change is Always nil. (⇨ Objective-Cで表現できるプロパティではない。)
                print(change)

                //TableViewを更新(セクション単位で更新するメソッドを用意)
                self.updateSection(xxx: status.xxx)
            }

            self.keyValueObservations.append(kvo)
        }
    }
```


####セクションをたたむ
[こちらの記事](https://qiita.com/woxtu/items/c2e3fcfb5fd2005b43e2) の「セクションをたたむ様子」 が好きなので、無駄に多用してます。
(コードは参考にさせてもらった記事がとてもわかりやすいので割愛 ^^;


####セルをボタン的に扱う
あとは、UIButtonがあるカスタムセルの用意がめんどくさいときは、特定のセルをボタンのように扱っています。
ひとつのセルに触れた時だけ、選択状態にしています。

```swift:ViewController.swift
extension ViewController: UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        //セルの作成
        let cell = self.createCell(tableView, indexPath)
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        
        //セルに触れた時にボタンとして振る舞う場合のみ indexPath を返す
        if (self.isTouchCellIndexPath(indexPath)) {
            return indexPath
        }
        
        return nil
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //print(indexPath)
        
        //セルから指を離したときにコマンドを実行
        self.onClickCell(indexPath)
    }
}

extension ViewController {

    //セルの作成
    internal func createCell(_ tableView: UITableView, _ indexPath: IndexPath) -> UITableViewCell {
        
        //セルの作成
        let cell = UITableViewCell(style: .value1, reuseIdentifier: "cell")
        
        if (true) {
            //デバッグ用
            cell.textLabel?.text        = TableDef.row(indexPath)
            cell.detailTextLabel?.text  = self.getString(indexPath)
            //cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
        }
        
        //選択状態 (セルによっては有効にしたい)
        cell.selectionStyle = self.isTouchCellIndexPath(indexPath) ? (.default) : (.none)
        
        //指を離したときに .selectionStyle を元に戻したい
        if (cell.selectionStyle == .default) {
            
            // https://qiita.com/taketomato/items/e0e45f0d768109abef2f
            let gesture = UILongPressGestureRecognizer(target: self, action: #selector(tapCell(_:)))
            gesture.minimumPressDuration = 0
            cell.addGestureRecognizer(gesture)
        }
        
        return cell
    }
}
```

セルをボタンとして扱うかどうかを判定するためのisTouchCellIndexPathメソッド内の処理は、固定セル前提の実装なので...このような実装をしています。

```swift:ViewController.swift
    fileprivate func isTouchCellIndexPath(_ indexPath: IndexPath) -> Bool {
        
        //タップしたい項目だけを登録
        let list: [Int : [Int]] = [
            TableDef.SectionRowIndex.section0.rawValue :
                [
                    TableDef.Section1RowIndex.cell_A.rawValue,
                    TableDef.Section1RowIndex.cell_B.rawValue,
                    TableDef.Section1RowIndex.cell_C.rawValue,
            ],
            
            TableDef.SectionRowIndex.section2.rawValue :
                [
                    TableDef.Section1RowIndex.cell_B.rawValue,
            ],
        ]
        
        return list[indexPath.section]?.contains(indexPath.row) ?? false
    }
```


全体的なViewControllerのコードは [gist](https://gist.github.com/takashi1975/7eea1235666a49b7a394887e03ad502d) にアップしてみました。(XCode 9.2 で確認)



###まとめながら気づいた点
Cell作成時にaddGestureRecognizerしたけど...破棄のことを考慮してなかった...。 ^^;