为什么UITableView中异步取消选中UITableViewCell时动画不生效?
问题原因
- UIKit处理cell点击的过程中,
didSelectRowAt回调触发时,系统已经将cell的isSelected属性标记为true,但对应的选中状态视觉渲染,会在当前RunLoop周期的所有同步任务执行完成后才会提交到渲染层。 DispatchQueue.main.async派发的任务会被加入主队列的下一个RunLoop周期执行,此时刚好赶上系统刚完成选中状态的帧渲染,你立刻调用取消选中,两者的时间差远低于人眼可识别的16ms单帧阈值,所以看起来就像没有选中过程,取消动画也完全看不到。- 额外的,UITableView默认开启
delaysContentTouches,会对触摸事件加100ms左右的延迟来判断是否是滚动操作,你直接异步派发的取消操作刚好卡在这个延迟的空窗期,导致选中和取消的动画直接被合并抵消。
解决方案
方案1:添加小幅延迟后执行取消选中
给异步取消操作加100ms左右的延迟,等系统的选中状态渲染完成后再执行,几乎不会产生可感知的卡顿:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { tableView.deselectRow(at: indexPath, animated: true) }
这个方案适合大部分场景,对原有逻辑侵入最小。
方案2:手动保证cell处于选中状态后再取消
如果你的异步操作本身耗时很长(比如等待网络请求返回),回来的时候cell可能已经因为复用等原因不在选中状态,此时可以先手动把cell设为选中,再执行取消,就能保证动画正常播放:
// 异步操作完成后的回调里执行 if let cell = tableView.cellForRow(at: indexPath) { cell.isSelected = true } tableView.deselectRow(at: indexPath, animated: true)
方案3:关闭触摸延迟
如果你不需要tableView的默认触摸延迟判断,可以直接关闭delaysContentTouches,也能解决这个问题:
override func viewDidLoad() { super.viewDidLoad() tableView.delaysContentTouches = false // 其他初始化逻辑 }
这个方案会让整个tableView的点击响应更灵敏,适合以点击交互为主、滚动操作较少的列表场景。
内容的提问来源于stack exchange,提问作者Daniel Wood
相关产品推荐
相关产品推荐

