【iOS】關(guān)于tableview單選cell的一點(diǎn)嘗試和見(jiàn)解

2017.09.19更新

  • 最近發(fā)現(xiàn)可以利用cell.isSelected屬性更好更方便地去做單選cell的設(shè)置
不錯(cuò)的單選效果

原理分析

  • 其實(shí)我不推薦使用cell.isSelected屬性的原因是,cell的單選和多選,本質(zhì)上是由tableview來(lái)統(tǒng)一控制管理的,手動(dòng)設(shè)置selected屬性本質(zhì)上是并不能起到“選中”cell的作用;
  • 解決方法是在viewWillAppear,或tableView的網(wǎng)絡(luò)數(shù)據(jù)加載完成后的回調(diào)里,通過(guò)
let selectedRow = IndexPath(row: 0, section: 0)
tableView.selectRow(at: selectedRow, animated: false, scrollPosition: .none)

這種方式來(lái)設(shè)置最初的默認(rèn)選中行;

  • 另外tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell這個(gè)方法其實(shí)是tableview的數(shù)據(jù)源方法,在里邊做cell的selected是本身不合理的,因?yàn)閏ell還沒(méi)有完全被return和display,更別提操作cell了;

代碼實(shí)現(xiàn)

  • 重寫(xiě)cell的setSelected方法
override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    
    accessoryType = selected ? .checkmark : .none
}
  • 設(shè)置默認(rèn)選中的行
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

// 由于我這個(gè)例子中的數(shù)據(jù)都是dead,所以tableView很快創(chuàng)建好,你可以在網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求的回調(diào)中這樣操作
    tableView.reloadData()
    tableView.beginUpdates()
    tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: .top)
    tableView.endUpdates()
}

舊方法

先看一個(gè)demo:

單選

Note: 功能上看起來(lái)是沒(méi)什么問(wèn)題,可以實(shí)現(xiàn)正常的單選選中,但大家有沒(méi)有發(fā)現(xiàn)一個(gè)細(xì)節(jié)性的bug:選中后面的cell時(shí)候,tableview會(huì)跳一下,然后到自動(dòng)滾到頂部了

原因

  • 實(shí)現(xiàn)上通過(guò)data的屬性,控制當(dāng)前indexPath對(duì)應(yīng)cell的選中狀態(tài);
  • 每次選中cell,讓當(dāng)前cell所對(duì)應(yīng)的數(shù)據(jù)屬性selected變成true,其他所有都為false;
  • reloadData();//這才是導(dǎo)致選中后,tableview會(huì)“跳”一下的根本原因
typealias Item = (title: String, selected: Bool)
private var data = [Item]()

// 選中
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
     tableView.deselectRow(at: indexPath, animated: true)
     
// 改變數(shù)據(jù)源控制屬性值
     data[indexPath.row].selected = true
// 刷新表格
    tableView.reloadData()
 }

// 復(fù)用
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
// 當(dāng)前選中的cell樣式
        cell.accessoryType = data[indexPath.row].selected ? .checkmark : .none
        
        return cell
}

改進(jìn)

嘗試一:
  • 用一個(gè)私有全局變量記錄上一次選中的cell的indexPath;
  • 當(dāng)cell選中的時(shí)候,獲得選中的cell,并立即設(shè)置其選中的樣式,然后將上一次選中的cell樣式置反;
  • 不要reloadData();

屬性設(shè)置

// 默認(rèn)選中第一行
private var selectedIndex = IndexPath(row: 0, section: 0)

// 選中cell樣式設(shè)置
private func setAccessoryTypeOfCellAt(index: IndexPath, selected: Bool){
        let cell = tableView.cellForRow(at: index)
        cell?.accessoryType = selected ? .checkmark : .none
}

選中

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
// 取消選中上一個(gè)cell
        setAccessoryTypeOfCellAt(index: selectedIndex, selected: false)
// 選中當(dāng)前cell
        setAccessoryTypeOfCellAt(index: indexPath, selected: true)
// 記錄indexPath,便于cell復(fù)用時(shí)能正確顯示其樣式
        selectedIndex = indexPath
}

復(fù)用

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        cell.textLabel?.text = data[indexPath.row]
        cell.accessoryType = indexPath == selectedIndex ? .checkmark : .none

        return cell
}

改進(jìn)效果

不會(huì)跳動(dòng)

見(jiàn)解

  • 如果cell比較少,不足以達(dá)到滿屏復(fù)用的時(shí)候,可以考慮第一種reloadData的方式;
  • 如果cell較多,則要考慮單選“跳動(dòng)”的問(wèn)題;
  • 如果你有更好的方式,歡迎留言評(píng)論,我便于擴(kuò)充整理;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容