Table View Controller中已调度通知列表显示异常求助
问题诊断与解决方案
我一眼就看出问题出在异步操作的时机把控和UI更新的线程正确性上,这也是iOS开发中处理异步数据加载时常见的坑!
问题根源
- 异步方法的执行顺序问题:
UNUserNotificationCenter.getPendingNotificationRequests是一个异步方法,它的回调会在后台线程执行。你在viewWillAppear里调用generateReminders()后立刻调用tableView.reloadData(),这时候回调还没执行,reminders数组还是空的,所以tableView一开始显示不出内容。 - UI更新未在主线程执行:你在后台线程的回调里修改了
reminders和remindersCount,但后续的UI刷新没有切回主线程,虽然数组操作本身线程安全,但tableView的刷新必须在主线程完成,否则会导致显示不稳定。 - 数据一致性问题:
cellForRowAt里依赖remindersCount判断索引,而remindersCount是在回调里赋值的,这可能导致数组长度和count值不一致,进一步加剧显示异常。
修复步骤
1. 修正generateReminders方法,确保主线程更新UI
把数据处理后的tableView刷新操作放到主线程,并且先清空旧数据避免重复:
func generateReminders() { // 先清空旧数据,防止重复添加 reminders.removeAll() let center = UNUserNotificationCenter.current() center.getPendingNotificationRequests { [weak self] notifications in guard let self = self else { return } for item in notifications { if let trigger = item.trigger as? UNCalendarNotificationTrigger, let triggerDate = trigger.nextTriggerDate() { // 简化withSound的判断逻辑 let withSound = item.content.sound == UNNotificationSound.default let reminder = Reminder( identifier: item.identifier, time: triggerDate, message: item.content.body, withSound: withSound, isAPendingNotification: true ) self.reminders.append(reminder) } } self.remindersCount = notifications.count // 必须回到主线程刷新UI DispatchQueue.main.async { self.tableView.reloadData() } } }
2. 调整viewWillAppear,移除提前的reloadData
现在刷新操作已经放在异步回调的主线程里了,不需要在viewWillAppear里手动调用:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) generateReminders() // 移除这里的tableView.reloadData(),等待数据准备好后再刷新 }
3. 优化cellForRowAt的逻辑,保证数据一致性
直接用reminders.count判断索引,避免依赖单独的remindersCount变量:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Reminder", for: indexPath) // 先判断索引是否在数组范围内 guard indexPath.row < reminders.count else { cell.textLabel?.text = "" cell.detailTextLabel?.text = "" return cell } let reminder = reminders[indexPath.row] let formatter = DateFormatter() formatter.dateFormat = "HH:mm" cell.textLabel?.text = formatter.string(from: reminder.time) // 建议Swift属性用小驼峰命名,Time改成time更规范 cell.detailTextLabel?.text = reminder.message return cell }
4. 简化viewWillDisappear方法
tableView.setNeedsDisplay()是多余的,清空数据即可:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) reminders.removeAll() remindersCount = 0 }
为什么这样能解决问题?
- 现在数据加载完成后才会触发tableView刷新,保证刷新时
reminders数组已经有完整数据。 - 所有UI操作都在主线程执行,符合iOS的UI线程安全要求。
- 移除了冗余的
remindersCount依赖,避免数据不一致导致的显示异常。
内容的提问来源于stack exchange,提问作者Alex Marchant
相关产品推荐
相关产品推荐

