打开UIPickerView报错:更新可见单元格时调用cellForRowAtIndexPath
触发原因
核心错误是你在UIPickerView的行渲染数据源回调里,主动触发了选中行的视图更新操作:
- UIPickerView内部依赖
UIPickerTableView(UITableView的私有子类)实现行展示,当picker加载、刷新可见行的过程中,会逐行调用pickerView(_:titleForRow:forComponent:)方法获取每行要展示的标题,这个阶段系统处于UITableView的可见单元格更新流程中,严格禁止任何会触发新的单元格查询、视图更新的操作。 - 你在
titleForRow回调里写了activity_UIPickerView.selectRow( row, inComponent: component, animated: true)逻辑,当遍历到匹配历史选中值的行时,这个调用会让内部的tableview在更新流程中再次触发cellForRowAtIndexPath:调用,直接命中系统的保护断言,抛出你看到的报错。 - 额外的代码逻辑问题:你把选项数据初始化、默认选中值赋值的逻辑全写在
titleForRow里,而这个方法会被系统在加载、滚动、刷新时反复调用(从你的日志里也能看到数据源方法被多次触发),会导致逻辑重复执行,本身就不符合数据源方法单一职责的设计要求。
修复方案
- 移除
titleForRow回调里所有和数据初始化、选中行相关的逻辑,这个方法只保留「返回对应行展示标题」的单一功能,绝对不能在picker的行渲染、行点击等正在更新视图的回调里,调用会触发picker视图重载、选中位置变更的方法。 - 把选项数据初始化、默认选中值计算的逻辑,移到
open_activity_picker()方法里,在把pickerView添加到视图之后,统一计算需要默认选中的行号,再调用selectRow方法设置选中位置。 - 初始化阶段设置默认选中行时,把
animated参数设为false,开启动画会在视图首次加载阶段额外插入动画更新逻辑,更容易触发更新流程冲突。
修正后代码示例
open_activity_picker 方法
func open_activity_picker() { activity_UIPickerView = UIPickerView() activity_UIPickerView.delegate = self activity_UIPickerView.dataSource = self activity_UIPickerView.backgroundColor = UIColor.white activity_UIPickerView.setValue(UIColor.black, forKey: "textColor") activity_UIPickerView.autoresizingMask = .flexibleWidth activity_UIPickerView.contentMode = .center activity_UIPickerView.frame = CGRect(x: 0.0, y: 10, width: UIScreen.main.bounds.size.width, height: 300) self.view.addSubview(activity_UIPickerView) // 提前初始化选项映射数据,不依赖数据源回调触发 let rawActivityList = [ "aerobics", "bike (mountain)", "bike (street)", "calisthenics", "golf 9 holes", "golf 18 holes", "hike moderate", "hike strenuous", "jog", "karate kata", "run", "stationary exercises", "swim", "treadmill", "walk", "woopie" ] activity_selection_types = rawActivityList.map { $0.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: " ", with: "_") } // 计算默认选中行 var targetSelectedRow = 0 if activity_selection_type.isEmpty { activity_selection_type = activity_selection_types[0] } else if let matchIndex = activity_selection_types.firstIndex(of: activity_selection_type) { targetSelectedRow = matchIndex } // picker加载完成后设置选中,初始化阶段关闭动画 activity_UIPickerView.selectRow(targetSelectedRow, inComponent: 0, animated: false) }
titleForRow 数据源方法
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { // 仅做标题返回,不掺杂任何视图更新、数据初始化逻辑 let showName = activity_selection_types[row].replacingOccurrences(of: "_", with: " ") return String(format: "%2d. %@", row + 1, showName).trimmingCharacters(in: .whitespacesAndNewlines) }
调整后,UIPickerView在更新可见单元格的流程中不会被插入额外的更新请求,断言报错会完全消失,同时也避免了数据源方法重复调用导致的逻辑异常。
内容的提问来源于stack exchange,提问作者Doug Null
相关产品推荐
相关产品推荐

