You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 23:05:32