2017.09.19更新
- 最近发现可以利用cell.isSelected属性更好更方便地去做单选cell的设置
原理分析
- 其实我不推荐使用cell.isSelected属性的原因是,cell的单选和多选,本质上是由tableview来统一控制管理的,手动设置selected属性本质上是并不能起到“选中”cell的作用;
- 解决方法是在viewWillAppear,或tableView的网络数据加载完成后的回调里,通过
let selectedRow = IndexPath(row: 0, section: 0)
tableView.selectRow(at: selectedRow, animated: false, scrollPosition: .none)
这种方式来设置最初的默认选中行;
- 另外
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
这个方法其实是tableview的数据源方法,在里边做cell的selected是本身不合理的,因为cell还没有完全被return和display,更别提操作cell了;
代码实现
- 重写cell的setSelected方法
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
accessoryType = selected ? .checkmark : .none
}
- 设置默认选中的行
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 由于我这个例子中的数据都是dead,所以tableView很快创建好,你可以在网络数据请求的回调中这样操作
tableView.reloadData()
tableView.beginUpdates()
tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: .top)
tableView.endUpdates()
}
旧方法
先看一个demo:
Note
: 功能上看起来是没什么问题,可以实现正常的单选选中,但大家有没有发现一个细节性的bug:选中后面的cell时候,tableview会跳一下,然后到自动滚到顶部了
。
原因
- 实现上通过data的属性,控制当前indexPath对应cell的选中状态;
- 每次选中cell,让当前cell所对应的数据属性selected变成true,其他所有都为false;
- reloadData();//
这才是导致选中后,tableview会“跳”一下的根本原因
typealias Item = (title: String, selected: Bool)
private var data = [Item]()
// 选中
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// 改变数据源控制属性值
data[indexPath.row].selected = true
// 刷新表格
tableView.reloadData()
}
// 复用
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// 当前选中的cell样式
cell.accessoryType = data[indexPath.row].selected ? .checkmark : .none
return cell
}
改进
尝试一:
- 用一个私有全局变量记录上一次选中的cell的indexPath;
- 当cell选中的时候,获得选中的cell,并立即设置其选中的样式,然后将上一次选中的cell样式置反;
- 不要reloadData();
属性设置
// 默认选中第一行
private var selectedIndex = IndexPath(row: 0, section: 0)
// 选中cell样式设置
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)
// 取消选中上一个cell
setAccessoryTypeOfCellAt(index: selectedIndex, selected: false)
// 选中当前cell
setAccessoryTypeOfCellAt(index: indexPath, selected: true)
// 记录indexPath,便于cell复用时能正确显示其样式
selectedIndex = indexPath
}
复用
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
}
改进效果
见解
- 如果cell比较少,不足以达到满屏复用的时候,可以考虑第一种reloadData的方式;
- 如果cell较多,则要考虑单选“跳动”的问题;
- 如果你有更好的方式,欢迎留言评论,我便于扩充整理;