Reputation: 939
For some reason I get an index out of bounds error when using state (with an array) and binding with one of its values. In general there is no problem adding more values to the array. However when you try and delete a value, you get an index out of bound error.
This is a simplified version the problem I have in my own project.
Try the sample below in SwiftUI. Simply hold one of the circle to try and delete one! When it deletes there will be a Swift error:
Fatal error: Index out of range: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
I believe the error comes from the fact that the value being deleted is being bound by one of the CustomView
's value
. On deletion the view no longer has access to that value, triggering the out of bounds error.
import SwiftUI
struct Test: View {
@State var values: [Int] = [0, 1, 1, 1]
var totalBalls: Int {
return values.count
}
var body: some View {
HStack {
Text("\(totalBalls)")
VStack {
ForEach(0..<values.count, id: \.self) { i in
CustomView(value: self.$values[i])
}
.onLongPressGesture {
self.values.removeLast() //this line causes an error!
}
}
}
}
}
struct CustomView: View {
@Binding var value: Int
var body: some View {
ZStack {
Circle()
Text("\(value)").foregroundColor(Color.orange)
}.onTapGesture {
self.value+=1
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
Upvotes: 0
Views: 497
Reputation: 257493
There are two reasons in this case: constant ForEach, and refresh racing with direct biding.
Here is a solution that fixes crash and works as expected. Tested with Xcode 11.4 / iOS 13.4.
struct TestDeleteLast: View {
@State var values: [Int] = [0, 1, 1, 1]
var totalBalls: Int {
return values.count
}
var body: some View {
HStack {
Text("\(totalBalls)")
VStack {
// use index as id in ForEach
ForEach(Array(values.enumerated()), id: \.0.self) { i, _ in
CustomView(value: Binding( // << use proxy binding !!
get: { self.values[i] },
set: { self.values[i] = $0 }))
}
.onLongPressGesture {
self.values.removeLast()
}
}
}
}
}
Upvotes: 2