iOS Development

Quick tip: Avoid crash when using ForEach & Bindings in SwiftUI

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”

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?

Leave a Reply