11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-12-05

はじめに

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

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

single.gif multiple.gif

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

悩んだ点について

selectRow(at:animated:scrollPosition:)deselectRow(at:animated:)
※ 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)
        }
    }
}

参考にさせていただいた記事

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

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?