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

UITableView滚动时自定义Cell中UIButton标题状态丢失问题

Hey there! Let's fix that button state loss issue when your UITableView scrolls. The root problem here is cell reuse: when cells scroll off-screen, they get recycled for new rows, and your current code resets the button to its default state every time cellForRowAt runs—completely ignoring the selected state you set earlier. Here's how to fix it step by step:

1. Add a selection state property to your data model

First, you need to track the selected state for each user in your data source (not just the button UI). If your user model is named something like User, add a boolean property to it:

class User {
    // Your existing properties: firstname, lastname, jobTitle, avatar...
    var isSelected: Bool = false // Add this to track selection state
}

2. Update cellForRowAt to use the model's state

Instead of hardcoding the button's default state every time, configure it based on the isSelected value from your user model. Replace your current button setup code in cellForRowAt with this:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "FrendsTableViewCellId", for: indexPath) as! FriendEventialTableViewCell
    guard let f = self.user?[indexPath.row] else { return cell }
    
    // Configure basic cell content (unchanged)
    cell.userName.text = "\(f.firstname!) \(f.lastname!)"
    cell.titleName.text = f.jobTitle ?? ""
    if let avatarUrl = f.avatar {
        cell.imageUser.af_setImage(withURL: URL(string: avatarUrl)!)
    }
    
    // Configure button based on model's selection state
    if f.isSelected {
        cell.selectButton.setTitle("Selected", for: .normal)
        cell.selectButton.setTitleColor(ColorConstant.charcoalGrey, for: .normal)
        cell.selectButton.layer.borderColor = UIColor(red: 215.0/255.0, green: 190.0/255.0, blue: 10.0/255.0, alpha: 1.0).cgColor
        cell.selectButton.backgroundColor = ColorConstant.sunYellow
        cell.selectButton.isSelected = true
    } else {
        cell.selectButton.setTitle("Select", for: .normal)
        cell.selectButton.setTitleColor(ColorConstant.charcoalGrey, for: .normal)
        cell.selectButton.backgroundColor = .white
        cell.selectButton.layer.borderColor = UIColor(red: 151.0/255.0, green: 151.0/255.0, blue: 151.0/255.0, alpha: 1.0).cgColor
        cell.selectButton.isSelected = false
    }
    
    // Optional: Move button target setup to the cell class (see step 4)
    cell.selectButton.addTarget(self, action: #selector(followButtonClicked(sender:)), for: .touchUpInside)
    cell.selectButton.tag = indexPath.row
    
    return cell
}

3. Modify the button tap handler to update the model

Instead of only changing the button's UI, update the corresponding user model's isSelected state, then refresh the cell to reflect the change:

@objc func followButtonClicked(sender:UIButton) {
    guard let indexPath = tableView.indexPathForRow(at: sender.convert(.zero, to: tableView)) else { return }
    guard var user = self.user?[indexPath.row] else { return }
    
    // Toggle the selection state in the model
    user.isSelected = !user.isSelected
    self.user![indexPath.row] = user
    
    // Refresh the cell to apply the new state (avoids reuse issues)
    tableView.reloadRows(at: [indexPath], with: .automatic)
    
    // Optional: If you don't want to reload the row, you can manually update the button UI here
    // But reloading is more reliable for avoiding reuse bugs
}

Adding the button target in cellForRowAt can lead to multiple target attachments as cells are reused. Instead, move this setup to your custom cell class and use a closure to pass the tap event back to the view controller:

In FriendEventialTableViewCell:

class FriendEventialTableViewCell: UITableViewCell {
    // Your existing @IBOutlet connections
    @IBOutlet weak var userName: UILabel!
    @IBOutlet weak var titleName: UILabel!
    @IBOutlet weak var imageUser: UIImageView!
    @IBOutlet weak var selectButton: UIButton!
    
    // Closure to handle button taps
    var onButtonTap: (() -> Void)?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Add target once when the cell is initialized
        selectButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    }
    
    @objc private func buttonTapped() {
        onButtonTap?()
    }
}

Then update cellForRowAt to use the closure:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "FrendsTableViewCellId", for: indexPath) as! FriendEventialTableViewCell
    guard let f = self.user?[indexPath.row] else { return cell }
    
    // ... (keep your existing cell content configuration)
    
    // Set up the button tap closure
    cell.onButtonTap = { [weak self] in
        guard let self = self else { return }
        var updatedUser = f
        updatedUser.isSelected = !updatedUser.isSelected
        self.user![indexPath.row] = updatedUser
        self.tableView.reloadRows(at: [indexPath], with: .automatic)
    }
    
    return cell
}

The key takeaway here is: always store state in your data model, not just in UI controls. Since UITableView reuses cells, the UI will reset when cells scroll, but your model's state will persist—ensuring the cell always shows the correct state when it's reused.

内容的提问来源于stack exchange,提问作者Irwan Madness

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:14:48