如何解决Swift中UITableView向下滚动时cell数据异常刷新问题
问题根因
这是UITableView Cell复用机制导致的典型数据错乱,你的代码有两个核心错误:
- 没有单独存储每一条高光记录的快照数据,所有Cell渲染时都读取当前计时器、比分栏、事件输入框的实时值
- Cell滚出屏幕后会被系统回收进入复用池,当它再次被加载到屏幕上时,
cellForRowAt会重新执行,此时读到的就是最新的UI值,自然会覆盖原来记录的历史内容
另外你把设置Gamelog.backgroundColor的逻辑写在numberOfRowsInSection里是错误写法,这个方法会被系统频繁调用,只应该用来返回对应分区的行数,不要放任何UI配置代码。
修复方案
核心原则:UITableView的所有行内容必须由独立的数据源数组驱动,永远不要在cellForRowAt里读取和当前行无关的全局变量、实时UI控件值。
步骤1:定义单条记录的数据模型
用来保存每一次高光生成瞬间的所有信息,生成后就不会再变动:
struct GameLogRecord { let time: String let scoreA: String let scoreB: String let eventContent: String }
步骤2:用数组作为唯一数据源
替换你原来零散的NumberofScores、Gamelogtext全局变量,用数组存储所有历史记录:
// 你的ViewController里的属性 var gameRecords: [GameLogRecord] = [] let cellReuseIdentifier = "cell"
把原来写在numberOfRowsInSection里的UI配置移到viewDidLoad里执行:
override func viewDidLoad() { super.viewDidLoad() Gamelog.backgroundColor = .clear // 如果你是用Storyboard拖的Cell可以省略下面这行注册代码 Gamelog.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier) }
步骤3:重写TableView数据源方法
cellForRowAt里只从数据源数组取对应行的数据渲染,不读任何实时UI值:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return gameRecords.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // 安全的Cell复用写法 guard let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) else { return UITableViewCell(style: .default, reuseIdentifier: cellReuseIdentifier) } // 取当前行对应的历史记录 let record = gameRecords[indexPath.row] cell.textLabel?.text = "\(record.time) Score: \(record.scoreA)-\(record.scoreB) - \(record.eventContent)" cell.textLabel?.adjustsFontSizeToFitWidth = true cell.backgroundColor = UIColor(red: 0/255, green: 144/255, blue: 81/255, alpha: 1) cell.textLabel?.textColor = .white return cell }
步骤4:修改按钮点击的新增逻辑
点击按钮的瞬间就把当时的时间、比分、事件内容存成快照加入数据源,不要用单个全局变量临时存文本:
// 按钮点击触发新增记录 func addRecordButtonTapped() { // 生成记录快照,此时读取的UI值就是点击瞬间的值,后续UI变化不会影响这条记录 let newRecord = GameLogRecord( time: TimerLabel.text ?? "", scoreA: AScore.text ?? "0", scoreB: BScore.text ?? "0", eventContent: "\(TeamA.text ?? "") TRY \(A1.text ?? "")" ) // 必须先更新数据源,再操作TableView插入行 gameRecords.append(newRecord) Gamelog.performBatchUpdates({ Gamelog.insertRows(at: [IndexPath(row: gameRecords.count - 1, section: 0)], with: .automatic) }, completion: nil) }
补充提示:UITableView的Cell复用是系统的内存优化机制,不会保留屏幕外的Cell内容,所有和行绑定的内容必须存在独立的数据源中,这是iOS开发使用TableView的基础规则。
内容的提问来源于stack exchange,提问作者Jack Martin
相关产品推荐
相关产品推荐

