Swift中UITableView滚动时出现多余勾选标记的问题排查与解决
UITableViewCell复选框复用导致UI异常:选中第1个单元格时第5个也显示选中
在Storyboard的UITableViewCell中添加复选框后,表格有8个单元格时出现异常:选中第1个单元格,第5个单元格也会显示blue_tick;选中第2个单元格,第6个单元格同样显示选中状态。调试发现cellForRowAt方法并未进入if isfromDetails分支,即使注释该分支代码,异常仍存在,且选中的id并未错误添加到数组,仅UI显示异常。
问题代码
didSelectRowAt方法
var selectedArray:[Int] = [] func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if tableView == self.tableView{ let cell:PaymentCell = tableView.cellForRow(at: indexPath) as! PaymentCell if let indexData = eventListData?.result?.events?[indexPath.row] { if let index = selectedArray.firstIndex(of: indexData.id ?? 0) { cell.checkImg.image = UIImage(named: "checkbox_inactive") selectedArray.remove(at: index) } else { selectedArray.append(indexData.id ?? 0) cell.checkImg.image = UIImage(named: "blue_tick") } } } }
cellForRowAt方法及Cell类
var isfromDetails = false func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "PaymentCell", for: indexPath) as! PaymentCell let indexData = eventListData?.result?.events?[indexPath.row] cell.titleLbl.text = indexData?.event_title cell.addressLbl.text = indexData?.event_venue var floatVal: Float? if let standardRate = indexData?.ticket_price, let convValue = eventListData?.result?.conversion_factor { let priceVal = Float(standardRate) let convVal = Float(convValue) floatVal = (priceVal * convVal!) if let floatVal = floatVal { let formatter = NumberFormatter() formatter.maximumFractionDigits = 2 formatter.roundingMode = .down let roundedValue1: String = formatter.string(from: floatVal as NSNumber) ?? "" cell.priceLbl.text = "\(eventListData?.result?.user_curreny?.symbol ?? "GBP") \(roundedValue1)" } else { cell.priceLbl.text = "N/A" } } let bannerImage = indexData?.event_thumbnail ?? "" cell.bannerImg.getImage(subUrl: eventListImageBaseUrl, withImagePath: bannerImage, placeHolder: #imageLiteral(resourceName: "icon8")) cell.bannerImg.contentMode = .scaleAspectFit if isfromDetails{ let id = indexData?.id ?? 0 if selectedArray.contains(id){ cell.checkImg.image = UIImage(named: "blue_tick") }else{ cell.checkImg.image = UIImage(named: "checkbox_inactive") } } return cell } class EventListCell: UITableViewCell { @IBOutlet weak var checkImg: UIImageView! override func awakeFromNib() { selectionStyle = .none super.awakeFromNib() } }
问题原因
- Cell复用机制未正确处理:UITableView会复用滚出屏幕的Cell,你直接在
didSelectRowAt中修改Cell的复选框图片,但未在cellForRowAt中统一根据数据源重置状态。比如第1个Cell滚出后被复用为第5个Cell,此时复选框仍保留之前的blue_tick状态。 isfromDetails限制导致状态未初始化:isfromDetails默认值为false,导致cellForRowAt完全不处理复选框状态,复用的Cell无法重置为默认的未选中状态。
解决方案
1. 移除isfromDetails判断,统一在cellForRowAt中处理复选框状态
确保每个Cell在复用或初始化时,都根据selectedArray的数据源渲染正确的复选框状态:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "PaymentCell", for: indexPath) as! PaymentCell let indexData = eventListData?.result?.events?[indexPath.row] cell.titleLbl.text = indexData?.event_title cell.addressLbl.text = indexData?.event_venue var floatVal: Float? if let standardRate = indexData?.ticket_price, let convValue = eventListData?.result?.conversion_factor { let priceVal = Float(standardRate) let convVal = Float(convValue) floatVal = (priceVal * convVal!) if let floatVal = floatVal { let formatter = NumberFormatter() formatter.maximumFractionDigits = 2 formatter.roundingMode = .down let roundedValue1: String = formatter.string(from: floatVal as NSNumber) ?? "" cell.priceLbl.text = "\(eventListData?.result?.user_curreny?.symbol ?? "GBP") \(roundedValue1)" } else { cell.priceLbl.text = "N/A" } } let bannerImage = indexData?.event_thumbnail ?? "" cell.bannerImg.getImage(subUrl: eventListImageBaseUrl, withImagePath: bannerImage, placeHolder: #imageLiteral(resourceName: "icon8")) cell.bannerImg.contentMode = .scaleAspectFit // 移除isfromDetails判断,统一根据数据源设置复选框状态 let eventId = indexData?.id ?? 0 cell.checkImg.image = selectedArray.contains(eventId) ? UIImage(named: "blue_tick") : UIImage(named: "checkbox_inactive") return cell }
2. 修改didSelectRowAt,操作数据源后刷新对应Cell
不再直接修改Cell的UI,而是通过修改数据源后刷新Cell,确保UI与数据源一致:
var selectedArray:[Int] = [] func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard tableView == self.tableView, let indexData = eventListData?.result?.events?[indexPath.row] else { return } let eventId = indexData.id ?? 0 if let index = selectedArray.firstIndex(of: eventId) { selectedArray.remove(at: index) } else { selectedArray.append(eventId) } // 刷新当前行,触发cellForRowAt重新渲染UI tableView.reloadRows(at: [indexPath], with: .automatic) }
内容的提问来源于stack exchange,提问作者new dev
相关产品推荐
相关产品推荐

