I have an observable object that stores an array of contentItems (struct).
My "root" view owns the observable object and subviews are generated with ForEach.
Each subview has a textfield that should modify its content stored in the array.
This works fine until I use the delete button to remove one item of the array. This causes the view to crash. The error message says: "Fatal error: Unexpectedly found nil while unwrapping an Optional value". Of corse it can't find the index because it doesn't exist any more. Why is the subview still in the render loop??
For better understanding I simplified my code. This code runs on iOS/iPadOS
Simplified code:
import SwiftUI
class obs: ObservableObject {
@Published var contentArray : [contentItem] = []
func removeItem(id: UUID) {
contentArray.remove(at: contentArray.firstIndex(where: { $0.id == id })!)
}
}
struct contentItem {
var id : UUID = UUID()
var str : String = ""
}
struct ForEachViewWithObservableObjetTest: View {
@StateObject var model : obs = obs()
var body: some View {
VStack{
Button("add") { model.contentArray.append(contentItem()) }
.padding(.vertical)
ScrollView{
ForEach(model.contentArray, id: \.id) { content in
contentItemView(id: content.id, model: model)
}
}
}
}
}
struct contentItemView : View {
var id : UUID
@ObservedObject var model : obs
var body: some View {
HStack{
TextField("Placeholder", text: $model.contentArray[ model.contentArray.firstIndex(where: { $0.id == id })! ].str)
.fixedSize()
.padding(3)
.background(.teal)
.foregroundColor(.white)
.cornerRadius(7)
Spacer()
Image(systemName: "xmark.circle")
.font(.system(size: 22))
.foregroundColor(.red)
// tap to crash - I guess
.onTapGesture { model.removeItem(id: id) }
}.padding()
.padding(.horizontal, 100)
}
}
I were able to fix the issue by adding an if else check into the binding wrapper but this feels wrong and like a bad workaround.
TextField("Placeholder", text:
Binding<String>(
get: {
if let index = model.contentArray.firstIndex(where: { $0.id == id }) {
return model.contentArray[index].str
}
else { return "Placeholder" }
}, set: { newValue in
if let index = model.contentArray.firstIndex(where: { $0.id == id }) {
model.contentArray[index].str = newValue
}
else { }
}))
With this method I noticed that while deleting the subview, the textfield in the subview refreshes and thus causes the crash.
How can I fix this issue properly?
Arrayin your model have objects or not. So, may you are trying to remove an element from empty array.