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)
}
}
我可以通过在绑定包装器中添加if-else判断来解决这个问题,但这感觉是错误的,而且是一个糟糕的解决方法.
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?