UIKit:调用tableView.indexPathForRow(at:)时indexPath为何偏移1位?
Xcode14下UITableView indexPathForRow(at:)返回偏移的问题分析
这不是Apple的bug,你的代码存在两处关键错误:
1. 重复绑定按钮Target
在tableView(_:cellForRowAt:)中每次都调用cell.checkbox.addTarget(...),但UITableViewCell是复用机制,这会导致同一个按钮被多次绑定同一个action。当cell被复用时,旧的target不会自动移除,点击按钮时可能触发多次逻辑,或因复用导致坐标计算出现偏差。
2. 坐标转换的参考点错误
你使用sender.convert(CGPoint.zero, to: self.tableView)获取的是按钮左上角在tableView中的坐标,这个点可能不在当前cell的可视范围内(比如cell布局有内边距、按钮位置偏上时),导致indexPathForRow(at:)返回错误的行索引。
修正方案
方案一:避免重复绑定Target(推荐)
将按钮的Target绑定移到TestCell内部,通过闭包回调通知ViewController,彻底规避复用问题:
// TestCell.swift class TestCell: UITableViewCell { let nameLabel = UILabel() let checkbox = UIButton(type: .system) override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) // 仅初始化时绑定一次Target checkbox.addTarget(self, action: #selector(checkboxTapped), for: .touchUpInside) // 基础布局示例(按需调整) contentView.addSubview(nameLabel) contentView.addSubview(checkbox) nameLabel.translatesAutoresizingMaskIntoConstraints = false checkbox.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ nameLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), checkbox.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), checkbox.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func checkboxTapped() { tapHandler?() } // 通过闭包传递点击事件 var tapHandler: (() -> Void)? }
在ViewController中:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as! TestCell cell.nameLabel.text = "Row \(indexPath.row)" // 绑定闭包,直接获取当前indexPath cell.tapHandler = { [weak self] in guard let self = self else { return } print("IndexPath: \(indexPath)") } return cell }
方案二:修正坐标转换逻辑
如果坚持使用原有的Target方式,需要:
- 在cell复用前移除旧的Target
- 使用按钮的中心坐标进行转换
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as! TestCell cell.nameLabel.text = "Row \(indexPath.row)" // 先移除旧Target,再添加新的 cell.checkbox.removeTarget(nil, action: nil, for: .allEvents) cell.checkbox.addTarget(self, action: #selector(testButton(_:)), for: .touchUpInside) cell.checkbox.tag = indexPath.row return cell } @objc func testButton(_ sender: UIButton) { // 使用按钮中心坐标进行转换 let centerPoint = sender.convert(sender.center, to: self.tableView) guard let indexPath = tableView.indexPathForRow(at: centerPoint) else { return } print("Tag: \(sender.tag)") print("IndexPath: \(indexPath)") }
旧版Xcode中未出现问题,大概率是因为旧版本UITableView对indexPathForRow(at:)的坐标判断逻辑更宽松,或cell复用的内存管理机制不同,但这并不代表原有代码是正确的。
内容的提问来源于stack exchange,提问作者SN81
相关产品推荐
相关产品推荐

