TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

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      

Hacking with Swift is sponsored by Superwall.

SPONSORED Superwall lets you build & test paywalls without shipping updates. Run experiments, offer sales, segment users, update locked features and more at the click of button. Best part? It's FREE for up to 250 conversions / mo and the Superwall team builds out 100% custom paywalls – free of charge.

Learn More

Sponsor Hacking with Swift and reach the world's largest Swift community!

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.