如何为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
相关产品推荐
相关产品推荐

