[Swift] UITableViewの備忘録

#今回の題
todoリストを作る過程でTableViewを使ったので覚えている内にどこかに書き残しておこうという魂胆で書いてます。
特に物珍しいことはしていません。

シングルビューアプリ上のtableViewでセルの

- 表示
- 並び替え
- 削除

を行うくらいの内容です。

#諸々のバージョン

- Swift version 5.3
- Xcode Version 11.1 


#メインストーリーボード
以下の状態を作ります。

![スクリーンショット 2020-10-24 18.07.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/92c8c9d7-c050-36a1-13af-3b12bbc523ea.png)

流れは、

1. デフォルトのViewControllerにNavigationControllerを追加
2. ViewController上にtableViewを配置
3. ２のtableView上にtableViewCellを配置
4. tableViewCellのidentifierを`cell`に設定

です。

続いて、tableViewのdelegateとdataSourceもメインストーリーボードで設定しておきます。

![スクリーンショット 2020-10-24 18.33.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/75a23ee8-8248-4986-74b6-997cab4cf841.png)

これをしておけば、viewControllerで

```swift
tableView.delegate = self
tableView.datasource = self
```
とか書かずに済みます。
ただ、ここら辺の書き方は現場に合わせるのが一番かと思いますが、どちらが主流なんでしょうね🤔
Swift歴独学数週間の私にはわかりませんが、もしご存知の方がいらっしゃいましたらお教えいただけますと幸いです。


#TableViewに配列を表示する
![スクリーンショット 2020-10-24 19.53.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/c59e07c9-aecb-13d5-f971-e42c9915d49d.png)


##viewController

以下のように、tableViewをOutlet接続し、配列を定義します。

```swift:ViewController.swift
class ViewController: UIViewController {
    
    // 以下２行を追記
    private var todos = ["卵を買う", "牛乳を買う", "スポンジを買う", "イチゴを買う"]
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}
```

続いて、`セルの数`、`セルに表示する内容`を定義します。
これらはviewControllerクラス内に定義してもいいのですが、今回はviewControllerを汚さず、かつ可読性を高めるためにextensionを使ってプロトコルごとに定義していきます。

表示するには、UITableViewDataSourceプロトコルの必須メソッドを2つ使います。
以下をviewControllerクラスの下に書きます。

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

    // セルの数を定義。　ここでは配列の数のセルを指定。
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return todos.count
    }
    
    // セルの各行の内容を定義
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel!.text = todos[indexPath.row]
        return cell
    }
}
```

表示はこれで完成です。

#TableViewを操作する
TableViewを操作する際には、主に下の画像にあるメソッドとプロパティを用いて編集モードに切り替えて行います。
![スクリーンショット 2020-10-24 20.17.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/87e5ce09-aaf5-12e2-652a-1a05e989229b.png)
なのでまず以下でその流れ。

##編集モードに切り替える

###その１　ボタンを配置
こんな感じになります。
![スクリーンショット 2020-10-24 20.31.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/c24458e7-809e-7f68-6735-301105eb2d42.png)
まず、navigationBarに編集モードに切り替えるためのボタンを配置します。
ボタンは自前で用意するのではなく、最初に挙げた画像にある`editButtonItem`を使います。
ViewControllerクラスのviewDidLoad()メソッドを以下のように編集します。

```swift:ViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        // 追記　navigationBarの右側に編集モードに切り替えるためのボタンを配置
        navigationItem.rightBarButtonItem = editButtonItem
    }
```

###その2 メソッドを定義
ViewControllerクラスに以下のメソッドを定義します。
このメソッドはその1のボタンが押されると呼ばれるメソッドで、編集モードに移行する内容になっています。
メソッド内の２行目、`isEditing`はtrueであれで編集を許可、falseであれば不許可とするプロパティです。

```swift:ViewController.swift
    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        tableView.isEditing = editing
    }
```

これでボタンを押すと以下のように編集モードに切り替えられるようになりました。
![スクリーンショット 2020-10-24 21.53.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/c9e5debe-668b-dcfd-4df3-2ae5ca97e061.png)

##セルを並び替える
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/a74b3b6f-6f66-49c2-b082-269fa51086e0.gif)
UITableViewDelegateプロトコルのメソッドをextensionを定義して書き出します。
セルを並び替える為に定義するメソッドは以下の二つ。

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

    // セルの移動を許可する
    func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    // セル移動時の処理。 セルの移動前の位置と移動後の位置を入れ替える処理を行う。
    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        let sourceCellItem = todos[sourceIndexPath.row]
        guard let indexPath = todos.firstIndex(of: sourceCellItem) else { return }
        todos.remove(at: indexPath)
        todos.insert(sourceCellItem, at: destinationIndexPath.row)
    }
}
```

各メソッドの説明はコメントの通りです。

##セルを削除する

![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/79cf50cf-45b5-319a-0344-51102c0861d2.gif)


先ほどのextensionに追記。
追記定義するメソッドは以下の一つだけ。

```swift:ViewController.swift
extension ViewController: UITableViewDelegate {
    // 削除を行うメソッド
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        // 配列から削除
        todos.remove(at: indexPath.row)
        // セルから削除
        tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)
    }
}
```

ただこれだと編集モード時以外でも、セルを横にスワイプすると削除できてしまいます。
そうしたくない時は以下のメソッドを追記します。

```swift:ViewController.swift
    //　編集モード中だけ削除を可能に
    func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        if tableView.isEditing {
            return .delete
        }
        return .none
    }
```

#メモ書き 
##セルの複数選択を有効にする
今回は使いませんでしたが、調べたのでメモしておきます。
ViewControllerのviewDidLoad()に以下を追記します。

```swift:ViewController.swift
// 複数選択を可能にする
tableView.allowsMultipleSelectionDuringEditing = true
```
ただこれを書くと、編集モード中にスワイプで削除はできなくなります。

選択したcellのパスは、

```swift
tableView.indexPathsForSelectedRows
```
で取得できます。

以上！！！