Swift中UIPickerView滚动后消失,唤起时崩溃报数组越界
解决UIPickerView滚动消失+数组越界崩溃问题
看起来你遇到的问题主要来自两个方面:picker因tableView刷新失去焦点消失,以及组件切换时未处理选中行导致数组越界。我帮你梳理下问题点并给出修复后的代码:
问题根源分析
- 错误调用TableView数据源方法:你在
didSelectRow里直接用self.tableView(tableView, cellForRowAt: index3)获取cell,这是创建新cell的数据源方法,不是获取界面上已存在的cell,会导致界面状态异常,甚至让textField失去焦点,picker直接消失。 - 未处理组件切换时的选中行:当你切换第一个组件后,第二个组件的选中行没有重置,若新大洲对应的货币数组长度比之前的短,就会出现
row超出数组范围的崩溃。 - 可能重复填充数据源:
reloadArea()每次调用都会往dolls里加数据,若被多次调用会导致数据源重复,引发未知问题。
修复后的完整代码
class Doll { var continent: String var dolls: [String] init(continent: String, dolls: [String]) { self.continent = continent self.dolls = dolls } } class addStoreTableViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource { var picker = UIPickerView() var dolls = [Doll]() // 必须初始化数据源数组 var myDoll: String = "" // 假设你有这个属性 func reloadArea() { dolls.removeAll() // 先清空数组,避免重复添加数据 dolls.append(Doll(continent: "Asia", dolls: ["¥(CNY)","$(HKD)","P(MOP)","NT$(TWD)","¥(JPY)","₩(KRW)","฿(THB)","RM(MYR)","$(SGD)"])) dolls.append(Doll(continent: "Euro", dolls: ["£(GBP)","€(EUR)","Fr(CHF)","₤(TRY)","kr(SEK)"])) dolls.append(Doll(continent: "Amereica", dolls: ["$(USD)","$(CAD)"])) dolls.append(Doll(continent: "Austr", dolls: ["$(AUD)"])) } func numberOfComponents(in pickerView: UIPickerView) -> Int { return 2 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { if component == 0 { return dolls.count } else { let selectedIndex = pickerView.selectedRow(inComponent: 0) // 安全判断:避免选中索引超出数据源范围 guard selectedIndex < dolls.count else { return 0 } return dolls[selectedIndex].dolls.count } } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { if component == 0 { guard row < dolls.count else { return nil } return dolls[row].continent } else { let selectedIndex = pickerView.selectedRow(inComponent: 0) // 双重安全判断:防止数组越界 guard selectedIndex < dolls.count, row < dolls[selectedIndex].dolls.count else { return nil } return dolls[selectedIndex].dolls[row] } } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { if component == 0 { // 切换大洲时,重置第二个组件的选中行为0,避免越界 pickerView.selectRow(0, inComponent: 1, animated: true) pickerView.reloadComponent(1) } let selectContinent = pickerView.selectedRow(inComponent: 0) let selectDoll = pickerView.selectedRow(inComponent: 1) // 安全校验,防止数组越界 guard selectContinent < dolls.count, selectDoll < dolls[selectContinent].dolls.count else { return } let myDoll = dolls[selectContinent].dolls[selectDoll] self.myDoll = myDoll let index3 = IndexPath(row: 3, section: 0) // 正确获取界面上的cell,而非创建新cell if let cell3 = tableView.cellForRow(at: index3) as? addStoreTableViewCell { cell3.textIn.text = myDoll // 若必须reload行,添加以下代码保持picker不消失 // tableView.reloadRows(at: [index3], with: .fade) // cell3.textIn.becomeFirstResponder() } } override func viewDidLoad() { super.viewDidLoad() self.reloadArea() picker.delegate = self picker.dataSource = self picker.selectRow(0, inComponent: 0, animated: true) picker.selectRow(0, inComponent: 1, animated: true) picker.reloadAllComponents() } // 别忘了实现tableView的数据源方法,比如cellForRowAt override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "addStoreCell", for: indexPath) as! addStoreTableViewCell if indexPath.row == 3 { // 将picker设置为textField的输入视图 cell.textIn.inputView = picker } return cell } }
关键修复点说明
- 数据源防重复:
reloadArea()里先清空数组再添加数据,避免多次调用导致数据源重复。 - 安全判断:所有数组访问前都加索引校验,彻底杜绝越界崩溃。
- 组件选中行重置:切换第一个组件时,强制把第二个组件的选中行设为0,保证新数据源的行索引有效。
- 正确获取cell:用
tableView.cellForRow(at:)获取界面上的cell,避免创建新实例导致的状态异常。 - Picker保持显示:如果必须reload table row,调用
textIn.becomeFirstResponder()让输入框重新获取焦点,picker就不会消失。
内容的提问来源于stack exchange,提问作者Tech HiMi
相关产品推荐
相关产品推荐

