WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

SwiftUI ForEach loop in Picker causing crash... Sometimes

Forums > SwiftUI

@bci  

I am writing my first SwiftUI app, a form to collect information. A bunch of input, create a JSON file, upload to server.

Two of my TestFlight users sent in crash reports on one Picker element, the error was what looks like array out of bounds. The code looks like:

let perceivedGenderOptions = [
    "Male",
    "Female",
    "Transgender man/boy",
    "Transgender woman/girl",
    "Gender nonconforming",
]

            HStack {
                Image(systemName: "info.circle")
                    .opacity(0.3)
                    .onLongPressGesture(minimumDuration: 0.5) {
                        self.showGuidance05.toggle()
                    }
                Picker("Gender", selection: $perceivedGenderTag) {
                    ForEach(0 ..< perceivedGenderOptions.count) {
                        Text(perceivedGenderOptions[$0])
                    }
                }
                .pickerStyle(MenuPickerStyle())
                Spacer()
                Text(perceivedGenderOptions[perceivedGenderTag])
            }

The crash report is pointing to the ForEach loop. This is intermittent, most of the time the list generated has no problems. Am I using the ForEach wrong when using to generate the Picker list?

#5  0x0000000102885f90 in closure #1 in closure #2 in closure #2 in closure #1 in SectionPerceivedView.body.getter at SectionPerceivedView.swift:59

3      

Not sure what the full intent is, but based on your code, I built out simple view that seems to work. For your code, what is the purpose of the showGuidance05 Bool?

import SwiftUI

struct SectionPerceivedView: View {

    let perceivedGenderOptions = [
        "Male",
        "Female",
        "Transgender man/boy",
        "Transgender woman/girl",
        "Gender nonconforming",
    ]

    @State var showGuidance05: Bool = false
    @State var perceivedGenderTag: Int = 0

    var body: some View {
        VStack {
            Text(showGuidance05 == true ? "Showing Guidance" : "Not Showing Guidance")

            HStack {
                Image(systemName: "info.circle")
                    .opacity(0.3)
                    .onLongPressGesture(minimumDuration: 0.5) {
                        self.showGuidance05.toggle()
                    }
                Picker("Gender", selection: $perceivedGenderTag) {
                    ForEach(0 ..< perceivedGenderOptions.count) {
                        Text(perceivedGenderOptions[$0])
                    }
                }
                .pickerStyle(MenuPickerStyle())
                Spacer()
                Text(perceivedGenderOptions[perceivedGenderTag])
            }
        }.padding()
    }
}

struct SectionPerceivedView_Previews: PreviewProvider {
    static var previews: some View {
        SectionPerceivedView()
    }
}

3      

@bci  

I built out simple view that seems to work.

Yes, mine also works, about 90% of the time...

what is the purpose of the showGuidance05 Bool?

It displays a string for the user to understand the purpose of the question (exerpt fro the manual ;) when they long press the (i):

            if showGuidance05 {
                Text(guidance05)
            }

I was able to reporduce the error, and received the following from Xcode:

The Xcode marker is on the [ for Text(perceivedGenderOptions[$0])

ForEach<Range<Int>, Int, Text> count (5) != its initial count (7). `ForEach(_:content:)` should only be used for *constant* data. Instead conform data to `Identifiable` or use `ForEach(_:id:content:)` and provide an explicit `id`!
Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
2020-12-07 20:01:56.027709-0800 GPD-Ripa[59903:2910943] Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444

Looks like I may need to supply an id. I thought that was only needed for custom types, this is just an array of strings. I'll change the ForEach loop to use an id and run it a few times to see if I can get it to fail again. It took 12 runs for me to get Xcode to spit out that error running in the simulator.

3      

Did you try:

ForEach(perceivedGenderOptions) { option in
         Text(option)
}

This should avoid the use of index and might help avoid the problem altogether.

3      

@bci  

I've added id: and have not been able to cause a crash running it 30 times. Code now looks like:

                Picker("Gender", selection: $perceivedGenderTag) {
                    ForEach(0 ..< perceivedGenderOptions.count, id: \.self) {
                        Text(perceivedGenderOptions[$0])
                    }
                }

I needed the index, as that is what I need to send up stream in the JSON data.

Thank you for all your help - we'll see if the testers can cause this (and the other 8 Pickers that use this scheme) index out of bounds crash again, or if this id: was the solution.

3      

Thank you for all your help - we'll see if the testers can cause this (and the other 8 Pickers that use this scheme) index out of bounds crash again, or if this id: was the solution.

Have you found the solution? I have the same issue even with the id:

 List{
                            ForEach(0..<tasksManager.tasks.count, id: \.self){ i in
                                    Button(action: {showTaskDetail.toggle()}, label: {
                                        TaskRow(task: $tasksManager.tasks[i])
                                    })
                                    .contextMenu(ContextMenu(menuItems: {
                                        Button(action: {tasksManager.tasks[i].isDone.toggle()}, label: {
                                            Label("Undone.", systemImage: "xmark")
                                        })

                                    }))

                                    .sheet(isPresented: $showTaskDetail, content: {
                                        EditTaskView(task: $tasksManager.tasks[i])
                                            .environmentObject(TasksManager())
                                    })
                            }.onDelete(perform: { indexSet in
                                tasksManager.tasks.remove(atOffsets: indexSet)
                            })
                        }.listStyle(PlainListStyle())

3      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.