iOS分段控件切换时UITableViewCell文本不更新及索引越界问题
根因定位
- 分段切换回调逻辑缺失:未在UISegmentedControl的
valueChanged事件中,针对选中第二个分段的场景触发已购事件拉取,仅靠首次加载的逻辑永远不会拉取已购事件数据,表格切到已购分段时无有效数据可渲染。 - Firestore快照监听属性复用冲突:全量事件、已购事件的查询都绑定了同一个
docListener属性,后创建的监听会直接覆盖前一个监听的引用,不仅导致全量事件的实时更新失效,还会出现监听状态错乱。 - 异步请求与表格渲染存在竞态:切换分段时未先清空旧数据源、同步更新表格行数,当异步数据未返回时,
cellForRowAt会用旧的全量事件数组匹配新分段下的行索引,直接触发index out of range错误。 - 无意义的同步返回逻辑:
getPurchasedEventName方法内的快照请求是异步执行的,方法外层同步返回的gotherePurchasedEvents永远是初始空值,这个返回值无实际作用,易误导逻辑。
修复实现
1. 拆分独立快照监听属性
替换原有的单一docListener,为两个数据源分别维护独立的监听实例,避免互相覆盖:
// 独立监听实例 var allEventsListener: ListenerRegistration? var purchasedEventsListener: ListenerRegistration?
2. 重构数据拉取方法
去掉无效的同步返回值,在拉取数据前先移除旧监听,数据更新后仅对应当前选中分段时触发表格重载:
func fetchAllEvents() { allEventsListener?.remove() getStudentGrade { [weak self] (studentGrade) in guard let self = self else { return } if let gradeForStudent = studentGrade { self.getStudentID { (studentID) in if let idForStudent = studentID { self.allEventsListener = self.db.collectionGroup("events") .order(by: "time_created", descending: true) .whereField("school_id", isEqualTo: idForStudent) .whereField("for_grades", isEqualTo: gradeForStudent) .addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in if let error = error { print("Error loading all events: \(error.localizedDescription)") return } self.gothereEvents = querySnapshot?.documents.map { document in return EventName(eventName: (document.get("event_name") as? String) ?? "") } ?? [] if self.eventListSelector.selectedSegmentIndex == 0 { self.tableView.reloadData() } } } } } } } func fetchPurchasedEvents() { purchasedEventsListener?.remove() getStudentID { [weak self] (studid) in guard let self = self else { return } if let id = studid { self.purchasedEventsListener = self.db.collection("student_users/\(self.user?.uid)/events_bought") .whereField("school_id", isEqualTo: id) .order(by: "time_purchased", descending: true) .addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in if let error = error { print("Error fetching purchased events: \(error.localizedDescription)") return } self.gotherePurchasedEvents = querySnapshot?.documents.map { document in let eventName = document.get("event_name") as? String ?? "" return PurchasedEventName(purchasedEventName: eventName) } ?? [] if self.eventListSelector.selectedSegmentIndex == 1 { self.tableView.reloadData() } } } } }
3. 补充分段切换回调
在控制器加载完成时绑定分段切换事件,切换到对应分段时先清空旧数据源,再触发对应的数据拉取,消除渲染竞态:
override func viewDidLoad() { super.viewDidLoad() eventListSelector.addTarget(self, action: #selector(segmentChanged(_:)), for: .valueChanged) // 首次加载拉取全量事件 fetchAllEvents() } @objc private func segmentChanged(_ sender: UISegmentedControl) { if sender.selectedSegmentIndex == 0 { gotherePurchasedEvents = [] tableView.reloadData() fetchAllEvents() } else { gothereEvents = [] tableView.reloadData() fetchPurchasedEvents() } }
4. 给TableView代理增加安全校验
在单元格渲染前先清空复用文本,增加索引边界判断,避免偶发越界崩溃:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: Constants.CellDetails.studentDashCell, for: indexPath) cell.textLabel?.text = "" if eventListSelector.selectedSegmentIndex == 0, indexPath.row < gothereEvents.count { cell.textLabel?.text = gothereEvents[indexPath.row].eventName } else if eventListSelector.selectedSegmentIndex == 1, indexPath.row < gotherePurchasedEvents.count { cell.textLabel?.text = gotherePurchasedEvents[indexPath.row].purchasedEventName } return cell }
数据库权限、索引配置已通过独立检测验证正常,问题完全出在前端的事件绑定、监听复用、渲染竞态上,按上述逻辑调整后即可实现分段切换时的内容同步更新,同时解决偶发的索引越界错误。
内容的提问来源于stack exchange,提问作者dsonawave
相关产品推荐
相关产品推荐

