swift – App crashes when SwiftUI view with Picker disappears (since iOS 16)

0
217


We have an application with some ‘chat’ functionality where questions are asked and the user can answer with some predefined options: for every question a new view is presented. One of those options is a view with a Picker, since iOS 16 this Picker causes the app to crash when the view with the Picker disappears with following error: Thread 1: Fatal error: Index out of range positioned at class AppDelegate: UIResponder, UIApplicationDelegate {. In the log I can see this error: Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range.

To troubleshoot this issue I refactored the code to a bare minimum where the picker isn’t even used but still cause the error to occur. When I remove the Picker from this view it works again.

View where error occurs

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 {
            // When removing the Picker from this view the error does not occur anymore
            Picker("Please choose a value", selection: $selected) {
                ForEach(colors, id: \.self) {
                    Text($0)
                }
            }.pickerStyle(.wheel) // with .menu style the crash does not occur

            Text("You selected: \(selected)")

            Button("Submit", action: {
                // In this function I provide an answer that is always valid so I do not
                // have to use the Picker it's value
                questionVM.answerQuestion(...)

                // In this function I submit the answer to the backend.
                // The backend will provide a new question which can be again a Picker
                // question or another type of question: in both cases the app crashes
                // when this view disappears. (the result of the backend is provided to
                // the view with `DispatchQueue.main.async {}`)
                questionVM.submitAnswerForQuestionWith(questionId: question.id)
            })
        }
    }
}

Parent view where the view above is used (Note: even with all the animation related lines removed the crash still occurs):

struct QuestionContainerView: View {
    
    @EnvironmentObject() var questionVM: QuestionVM
    
    @State var questionVisible = true
    @State var questionId = ""
    
    @State var animate: Bool = false
    
    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: self.questionVM.scrollDirection == .Next ? .trailing : .leading),
                                removal: .opacity
                            ))
                            .zIndex(0)
                            .onAppear {
                                self.animate.toggle()
                            }
                            .environmentObject(questionVM)
                    } else {
                        EmptyView()
                    }
                }
            }
        }
        .onAppear {
            self.questionVM.getQuestion()
        }
        .onReceive(self.questionVM.$currentQuestion) { q in
            if let question = q, question.id != self.questionId {
                self.questionVisible = false
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                    withAnimation {
                        self.questionVisible = true
                        self.questionId = question.id
                    }
                }
            }
        }
    }
    
    func getViewForQuestion(question: Question) -> AnyView {
        switch question.questionType {
        case .Picker:
            return AnyView(TestPickerQuestion(question: question))
        case .Other:
            ...
        case ...
        }
    }
}

The app was made originally for iOS 13 but is still maintained: with every new version of iOS the app kept working as expected until now with iOS 16.