UITableViewをプログラムから選択する実装について(チェックマークが表示・非表示されない問題)

# はじめに
こんにちは。
以下のようにUITableViewを**コード**から選択したくなりました。

※左は**単一選択**,右は**複数選択**です。

<img width="200" alt="single.gif" src="https://qiita-image-store.s3.amazonaws.com/0/128250/d9b82a02-5772-1d2a-a44e-f2d83c1e0983.gif"> <img width="200" alt="multiple.gif" src="https://qiita-image-store.s3.amazonaws.com/0/128250/3a976306-5957-3f37-44ef-23db3faab7ba.gif">

左上のボタン等で選択しているのですが、実装に少し悩んだため書いて見たいと思います。
環境は(Xcode: 9.1, Swift:4.0.2)至らぬ点などコメント頂けたら幸いです。

# 悩んだ点について
[selectRow(at:animated:scrollPosition:)](https://developer.apple.com/documentation/uikit/uitableview/1614875-selectrow) や [deselectRow(at:animated:)](https://developer.apple.com/documentation/uikit/uitableview/1614989-deselectrow)
※ Apple Reference
> Calling this method does not cause the delegate to receive a tableView(_:willSelectRowAt:) or tableView(_:didSelectRowAt:) message, nor does it send UITableViewSelectionDidChange notifications to observers.

以上のようにコードからセルを選択・非選択した場合、delegateメソッドは呼ばれないようで、チェックマークをつけたりなどの処理をしていた場合はうまくUIに反映されません。

# 解決方法
**UITableViewの内部のdelegateメソッド**を(ViewControllerを通して)あるタイミングで呼び出してしまうのは少し嫌な感じがするのですが、以下の流れで動かすことができました。

```MainViewController.swift
//現在選択されているセルを解除することを通知します
if let selectedIndexPath = self.tableView.indexPathForSelectedRow {
    self.tableView(self.tableView, didDeselectRowAt: selectedIndexPath)
}
//セルを選択
self.tableView.selectRow(at: selectIndexPath, animated: false, scrollPosition: .none)
//セルが選択されることを通知します
self.tableView(self.tableView, didSelectRowAt: selectIndexPath)
```

# ソースコード
ページの下に記述した**Extension**を使って書いて見ました。合わせて見ていただけたらと思います。
**単一選択**
---

```MainViewController.swift
import UIKit

class MainViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UINib(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: "CustomTableViewCell")
        tableView.delegate = self
        tableView.dataSource = self
    }
    
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return UIStatusBarStyle.lightContent
    }
    
    @IBAction func segmentControlValueChange(_ sender: UISegmentedControl) {
        var selectIndexPath = IndexPath(row: 0, section: 0)
        switch sender.selectedSegmentIndex {
        case 0:
            selectIndexPath = IndexPath(row: 0, section: 0)
        case 1:
            selectIndexPath = IndexPath(row: 2, section: 0)
        case 2:
            selectIndexPath = IndexPath(row: 5, section: 0)
        default: return
        }
        self.tableView.selectRowWithSendNotification(at: selectIndexPath, animated: false, scrollPosition: .none)
    }
    
    @IBAction func deselectButtonAction(_ sender: Any) {
        self.tableView.deselectAllRows(animated: false)
    }
}

extension MainViewController: UITableViewDataSource {
    //テーブルの行数を返却
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    //テーブルの行ごとのセルを返却する
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath)
        cell.selectionStyle = .none
        return cell
    }
}

extension MainViewController: UITableViewDelegate {
    //セルが選択された時
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
        cell.accessoryType = .checkmark
    }
    //セルが非選択の時
    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
        cell.accessoryType = .none
    }
}
```

**複数選択**
---

```MainViewController.swift
import UIKit

class MainViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UINib(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: "CustomTableViewCell")
        tableView.delegate = self
        tableView.dataSource = self
        tableView.allowsMultipleSelection = true
    }
    
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return UIStatusBarStyle.lightContent
    }
    
    @IBAction func select0ButtonAction(_ sender: Any) {
        self.tableView.selectRowWithSendNotification(at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: .none)
    }
    
    @IBAction func select2ButtonAction(_ sender: Any) {
        self.tableView.selectRowWithSendNotification(at: IndexPath(row: 2, section: 0), animated: false, scrollPosition: .none)
    }
    
    @IBAction func select5ButtonAction(_ sender: Any) {
        self.tableView.selectRowWithSendNotification(at: IndexPath(row: 5, section: 0), animated: false, scrollPosition: .none)
    }
    
    @IBAction func deselectButtonAction(_ sender: Any) {
        self.tableView.deselectAllRows(animated: false)
    }
}

extension MainViewController: UITableViewDataSource {
    //テーブルの行数を返却
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    //テーブルの行ごとのセルを返却する
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath)
        cell.selectionStyle = .none
        return cell
    }
}

extension MainViewController: UITableViewDelegate {
    //セルが選択された時
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
        cell.accessoryType = .checkmark
    }
    //セルが非選択の時
    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
        cell.accessoryType = .none
    }
}
```

# 終わりに
delegate通知を送りながら`selectRow`または`deselectRow`する**Extension**を書いて見たのですが、メソッドの命名に自信がなくて、コメントでいい感じの命名があれば教えていただきたいのです:;(∩´﹏`∩);:

```UITableViewExtension.swift
extension UITableView {
    /**
    行をdelegateに通知しながら選択します
    */
    func selectRowWithSendNotification(at: IndexPath?, animated: Bool, scrollPosition: UITableViewScrollPosition) {
        if !allowsMultipleSelection {
            if let selectedIndexPath = indexPathForSelectedRow {
                self.delegate?.tableView?(self, didDeselectRowAt: selectedIndexPath)
            }
        }
        if let selectIndexPath = at {
            self.selectRow(at: selectIndexPath, animated: animated, scrollPosition: scrollPosition)
            self.delegate?.tableView?(self, didSelectRowAt: selectIndexPath)
        }
    }
    /**
    行の選択をdelegateに通知しながら解除します
    */
    func deselectRowWithSendNotification(at: IndexPath, animated: Bool)  {
        if let selectedIndexPath = indexPathForSelectedRow {
            self.deselectAllRows(animated: animated)
            self.delegate?.tableView?(self, didDeselectRowAt: selectedIndexPath)
        }
    }
    /**
    行の選択状態をdelegate先に通知しながら全て解除します
    */
    func deselectAllRows(animated: Bool) {
        guard let selectedIndexPaths = self.indexPathsForSelectedRows else { return }
        for selectedIndexPath in selectedIndexPaths {
            self.deselectRow(at: selectedIndexPath, animated: animated)
            self.delegate?.tableView?(self, didDeselectRowAt: selectedIndexPath)
        }
    }
}
```

# 参考にさせていただいた記事
- [Select tableview row programmatically stackoverflow](https://stackoverflow.com/questions/2035061/select-tableview-row-programmatically)

- [UITableView Apple Reference](https://developer.apple.com/documentation/uikit/uitableview)

見て頂いてありがとうございます。