I have run into this issue in SwiftUI. I want to be able to remove an item from an Array when the user presses on a button, but I get a "Thread 1: Fatal error: Index out of range" error when I try. This seems to have to do with the fact that IntView takes in a @Binding: if I make num
just a regular variable, the code works fine with no errors. Unfortunately, I need to be able to pass in a Binding to the view for my purposes (this is a simplified case), so I am not sure what I need to do so the Binding doesn't cause the bug.
Here is my code:
import SwiftUI
struct IntView: View {
@Binding var num: Int // if I make this "var num: Int", there are no bugs
var body: some View {
Text("\(num)")
}
}
struct ArrayBugView: View {
@State var array = Array(0...10)
var body: some View {
ForEach(array.indices, id: \.self) { num in
IntView(num: $array[num])
Button(action: {
self.array.remove(at: num)
}, label: {
Text("remove")
})
}
}
}
Any help is greatly appreciated!
CodePudding user response:
In your code the ForEach
with indicies
and id: \.self
is a mistake. The ForEach
View in SwiftUI isn’t like a traditional for loop. The documentation of ForEach
states:
/// It's important that the `id` of a data element doesn't change, unless
/// SwiftUI considers the data element to have been replaced with a new data
/// element that has a new identity.
This means we cannot use indices, enumerated or a new Array in the ForEach
. The ForEach
must be on the actual array of identifiable items. This is so SwiftUI can track the row Views moving around, which is called structural identity and you can learn about it in Demystify SwiftUI WWDC 2021.
So you have to change your code to something this:
import SwiftUI
struct Item: Identifiable {
let id = UUID()
var num: Int
}
struct IntView: View {
let num: Int
var body: some View {
Text("\(num)")
}
}
struct ArrayView: View {
@State var array: [Item] = [Item(num:0), Item(num:1), Item(num:2)]
var body: some View {
ForEach(array) { item in
IntView(num: item.num)
Button(action: {
if let index = array.firstIndex(where: { $0.id == item.id }) {
array.remoteAt(index)
}
}, label: {
Text("remove")
})
}
}
}