You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

验证步骤

  1. 运行修改后的代码,多次提交Picker问题
  2. 确认视图切换时不再出现崩溃
  3. 测试iOS 13-15版本,确保原有功能正常

内容的提问来源于stack exchange,提问作者Vama

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 13:45:31