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ò)充整理;