iOS 16下含Picker的SwiftUI视图消失时应用崩溃问题排查
iOS 16下Wheel样式Picker视图消失时崩溃问题修复
问题现象
我们开发的问答应用在iOS 16及以上版本中,包含Wheel样式Picker的视图消失时会触发崩溃,报错为Thread 1: Fatal error: Index out of range,错误栈指向Swift/ContiguousArrayBuffer.swift:600,崩溃定位在AppDelegate类定义处。
关键特征:
- 仅Wheel样式Picker会触发崩溃,Menu样式无此问题
- 即使不使用Picker的选中值,只要保留组件就会崩溃
- 移除Picker后应用恢复正常
- iOS 13-15版本无此问题
问题代码分析
崩溃场景出现在视图切换逻辑中:当用户提交Picker问题后,后端返回新问题,父视图通过切换questionVisible状态隐藏旧视图、显示新视图,此时包含Wheel Picker的旧视图销毁时触发崩溃。
核心问题视图代码
struct PickerQuestion: View { @EnvironmentObject() var questionVM: QuestionVM let question: Question var colors = ["A", "B", "C", "D"] @State private var selected = "A" var body: some View { VStack { Picker("请选择一个值", selection: $selected) { ForEach(colors, id: \.self) { Text($0) } }.pickerStyle(.wheel) // 仅该样式触发崩溃 Text("你选中了:\(selected)") Button("提交", action: { questionVM.answerQuestion(...) questionVM.submitAnswerForQuestionWith(questionId: question.id) }) } } }
父视图切换逻辑
struct QuestionContainerView: View { @EnvironmentObject() var questionVM: QuestionVM @State var questionVisible = true @State var questionId = "" var body: some View { VStack { HeaderView(...) Spacer() if questionVM.currentQuestion != nil { ZStack(alignment: .bottom) { if questionVisible { getViewForQuestion(question: questionVM.currentQuestion!) .transition(.asymmetric( insertion: .move(edge: questionVM.scrollDirection == .Next ? .trailing : .leading), removal: .opacity )) .environmentObject(questionVM) } else { EmptyView() } } } } .onAppear { questionVM.getQuestion() } .onReceive(questionVM.$currentQuestion) { q in if let question = q, question.id != questionId { questionVisible = false DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { withAnimation { questionVisible = true questionId = question.id } } } } } func getViewForQuestion(question: Question) -> AnyView { switch question.questionType { case .Picker: return AnyView(PickerQuestion(question: question)) case .Other: return AnyView(...) } } }
问题根源
这是iOS 16中Wheel样式Picker的UIKit底层bug:当Picker所在SwiftUI视图被销毁时,Picker的底层UIPickerView尝试访问已释放的数据源数组,导致数组越界崩溃。
解决方案
方案1:延迟Picker视图销毁(推荐)
在Picker视图即将消失时清空数据源,避免UIKit底层访问已释放的资源:
struct PickerQView: View { @EnvironmentObject() var questionVM: QuestionViewModel @State private var colors = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] @State private var selected: String? = nil let question: MinimalQuestion var body: some View { VStack { Picker("请选择一个值", selection: $selected) { ForEach(colors, id: \.self) { Text("\($0)") } }.pickerStyle(.wheel) Text("你选中了:\(selected ?? "")") Button("提交", action: { questionVM.submitQuestion(questionId: question) }) } .onDisappear { // 视图消失前清空数据源,阻断底层无效访问 self.colors.removeAll() } .onChange(of: selected) { value in if let safeValue = value { questionVM.answerQuestion(question: question, answer: safeValue) } } } }
方案2:替换为自定义WheelPicker(彻底解决)
如果方案1无法覆盖所有场景,可以使用第三方自定义WheelPicker组件,完全替代系统Wheel样式Picker,规避UIKit底层bug。
方案3:临时切换Picker样式
在iOS 16及以上版本中,临时将Wheel样式替换为Menu样式,绕过bug:
.pickerStyle(UIDevice.current.systemVersion >= "16.0" ? .menu : .wheel)
验证步骤
- 运行修改后的代码,多次提交Picker问题
- 确认视图切换时不再出现崩溃
- 测试iOS 13-15版本,确保原有功能正常
内容的提问来源于stack exchange,提问作者Vama
相关产品推荐
相关产品推荐

