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

如何为UITableView每个单元格单独存储点赞/取消点赞状态

问题核心原因

你现在所有单元格共用同一点赞状态,问题出在三个地方:

  • 用全局单值ButtonSettings.isButtonTapped存点赞状态,所有单元格读写的都是同一个变量,根本没法区分独立状态
  • cellForRowAt每次刷新单元格都会往likesArray里追加元素,数组数据无限冗余,读状态必然错乱
  • 单元格内点击点赞按钮后只改了全局变量,没把当前单元格的对应状态同步给控制器数据源,cell复用时就会出现状态串位。
实现方案

1. 清理冗余逻辑

删掉所有ButtonSettings.isButtonTapped相关的全局状态逻辑,点赞状态统一存在控制器的likesDict中,key优先用列表项的唯一业务ID(比如工单ID),如果用indexPath.row当key,列表插入、删除、排序时会出现状态错位。原来的Likes结构体、likesArray都可以删掉,直接用字典存取效率更高,不会出现重复数据问题。

2. 改造自定义Cell,增加点击回调

Cell不要自己持有全局状态,点击事件通过回调抛给控制器处理,用属性观察器自动同步按钮UI状态,同时增加复用重置逻辑避免串状态:

class ListTicketsModulCell: UITableViewCell {
    // 点赞点击回调,返回当前最新的点赞状态
    var likeTapHandler: ((Bool) -> Void)?
    var isButtonPressed: Bool = false {
        didSet {
            if isButtonPressed {
                likeButton.setImage(UIImage(systemName: "heart.fill"), for: .normal)
                likeButton.tintColor = .red
            } else {
                likeButton.setImage(UIImage(systemName: "heart"), for: .normal)
                likeButton.tintColor = .white
            }
        }
    }

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        likeButton.addTarget(self, action: #selector(likeButtonPressed), for: .touchUpInside)
    }

    @objc func likeButtonPressed() {
        isButtonPressed.toggle()
        likeTapHandler?(isButtonPressed)
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        isButtonPressed = false
        likeTapHandler = nil
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // 省略你原有的布局、configure方法代码
}

3. 控制器侧处理状态读写和持久化

页面加载时先从UserDefaults读取历史点赞状态,cellForRowAt里只做当前cell的状态赋值,点击回调里更新状态并持久化:

final class ListTicketsViewController: UIViewController {
    var likesDict: [Int: Bool] = [:]
    // 替换成你自己的列表数据源
    var tickets: [TicketModel] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        // 启动时读取本地存的点赞状态
        if let savedLikes = UserDefaults.standard.dictionary(forKey: "ticketsLikes") as? [Int: Bool] {
            likesDict = savedLikes
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! ListTicketsModulCell
        let ticket = tickets[indexPath.row]
        cell.configure(with: ticket)
        
        // 给当前cell赋值对应点赞状态,没存过默认是未点赞
        cell.isButtonPressed = likesDict[ticket.id] ?? false

        // 处理点赞点击
        cell.likeTapHandler = { [weak self] isLiked in
            guard let self = self else { return }
            // 更新内存中的状态
            self.likesDict[ticket.id] = isLiked
            // 同步存到UserDefaults做持久化
            UserDefaults.standard.set(self.likesDict, forKey: "ticketsLikes")
        }

        cell.backgroundColor = .systemBlue
        return cell
    }

    // 省略你原有的其他代码
}
注意点
  • 如果你的列表数据会动态增减、排序,一定要用数据本身的唯一ID当likesDict的key,不要用indexPath.row
  • 点赞数据量如果超过100条,不建议用UserDefaults存储,换用plist文件或者SQLite更稳定
  • 不要在cellForRowAt里做循环遍历字典、数组追加这类操作,这个方法滑动列表时会高频调用,会造成卡顿。

内容的提问来源于stack exchange,提问作者Mikl.hart

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 02:01:33