You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

自定义Cell按钮切换背景图遇复用问题,求解决方案

解决UITableViewCell复用导致按钮状态错乱的问题

嘿,这个UITableView复用导致按钮状态乱掉的问题我太熟了!咱们先拆解下你代码里的几个核心问题,再给出具体的修复方案:

问题根源分析

  • 重复添加点击事件:你在tableView(_:cellForRowAt:)里每次都给按钮加addTarget,cell复用时会重复绑定事件,导致一次点击触发多次方法。
  • 状态仅存在cell内部:你只靠按钮的isSelected保存状态,cell被复用时会带着之前的状态,没有从数据源同步正确的状态。
  • tag传递行信息不可靠:用indexPath.row当tag,一旦tableView有分区、数据增删,tag和实际对应的数据源位置就会不匹配。

具体修复方案

第一步:重构Cell内部的按钮逻辑

把按钮的点击事件绑定移到Cell内部,避免重复添加,同时用闭包把点击事件传递给控制器:

class FoneCell: UITableViewCell {
    lazy var xxxBtn : UIButton = {
        let btn = UIButton()
        btn.setImage(UIImage(named: "love_18x18_"), for: .normal)
        btn.setImage(UIImage(named: "love_on_20x20_"), for: .selected)
        // 在这里绑定点击事件,而不是在控制器里
        btn.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
        return btn
    }()
    
    // 定义闭包,用于通知控制器按钮的选中状态变化
    var onButtonTapped: ((Bool) -> Void)?
    
    @objc private func btnTapped(_ sender: UIButton) {
        sender.isSelected = !sender.isSelected
        // 把最新状态通过闭包传递给控制器
        onButtonTapped?(sender.isSelected)
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        // 复用前重置按钮状态,避免残留旧状态
        xxxBtn.isSelected = false
    }
}

第二步:在控制器中维护状态数据源

专门用一个数组保存每一行按钮的选中状态,cell显示时从这个数组取正确状态:

class YourViewController: UIViewController, UITableViewDataSource {
    // 你的业务数据源(替换成你实际的数据类型)
    var dataSource: [YourDataModel] = []
    // 保存每一行按钮的选中状态,初始值根据数据源数量设为false
    var buttonSelectedStates: [Bool] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 假设这里已经加载了业务数据源dataSource
        buttonSelectedStates = Array(repeating: false, count: dataSource.count)
        tableView.register(FoneCell.self, forCellReuseIdentifier: "Focid")
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Focid", for: indexPath) as! FoneCell
        
        // 核心:从状态数据源取当前行的状态,设置按钮的选中状态
        cell.xxxBtn.isSelected = buttonSelectedStates[indexPath.row]
        
        // 设置闭包,处理按钮点击后的状态更新
        cell.onButtonTapped = { [weak self] isSelected in
            guard let self = self else { return }
            // 更新状态数据源
            self.buttonSelectedStates[indexPath.row] = isSelected
            // 这里可以添加其他逻辑,比如发送网络请求更新服务器状态
        }
        
        return cell
    }
    
    // 别忘了实现numberOfRowsInSection等必要方法
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }
}

为什么这样能解决问题?

  • 避免重复绑定事件:按钮的点击事件只在Cell初始化时绑定一次,复用不会重复添加。
  • 状态持久化在数据源:所有选中状态都保存在控制器的buttonSelectedStates数组里,cell复用时从这里取正确状态,彻底摆脱复用带来的状态残留。
  • 闭包代替tag更安全:不用依赖tag传递行信息,闭包直接关联当前的indexPath,数据变动时也不会出错。
  • 弱引用避免内存泄漏:闭包里用[weak self]防止循环引用,保证内存安全。

内容的提问来源于stack exchange,提问作者BaiClassmate Xiao

火山引擎 最新活动