If you’ve played around with ForEach
and .onDelete
in SwiftUI you might have encountered a crash due to ‘Index out of range‘ when you delete elements that are referenced with a binding (eg. a Toggle
within your ForEach closure)
This occurs due to a race condition of sorts… SwiftUI is trying to read from the old binding after the index it references has already been deleted. In other words: it is trying to update the view that is about to be removed before the ForEach has been refreshed.
This is a bug in SwiftUI, and will hopefully be fixed with upcoming releases.
But for now… here is an extension on Binding that provides a ‘safe’ subscript that avoids this crash. Note that this only prevents a crash on subsequent access , you still need to provide a valid index when creating it (so that our binding remains non-optional).
extension Binding where Value: MutableCollection
{
subscript(safe index: Value.Index) -> Binding<Value.Element>
{
// Get the value of the element when we first create the binding
// Thus we have a 'placeholder-value' if `get` is called when the index no longer exists
let safety = wrappedValue[index]
return Binding<Value.Element>(
get: {
guard self.wrappedValue.indices.contains(index)
else { return safety } //If this index no longer exists, return a dummy value
return self.wrappedValue[index]
},
set: { newValue in
guard self.wrappedValue.indices.contains(index)
else { return } //If this index no longer exists, do nothing
self.wrappedValue[index] = newValue
})
}
}
Code language: PHP (php)
2 replies on “Quick tip: Avoid crash when using ForEach & Bindings in SwiftUI”
[…] There is a bug in how SwiftUI handles deletions from ForEach currently, see this post if you encounter crashes on deletion of […]
Hey, thanks for this so much! I’ve been fighting against this bug for so long now.
On that note though, how sure are you that this is a bug with `ForEach`, and not just incorrect usage of SwiftUI?