You need to enable JavaScript to run this app. iOS分段控件切换时UITableViewCell文本不更新及索引越界问题
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 22:24:06