[英]SwiftUI - Fatal error: index out of range on deleting element from an array
[英]SwiftUI out of index when deleting an array element in ForEach
我在这里查看了不同的问题,但不幸的是我找不到答案。 这是我的代码:
SceneDelegate.swift
...
let contentView = ContentView(elementHolder: ElementHolder(elements: ["abc", "cde", "efg"]))
...
window.rootViewController = UIHostingController(rootView: contentView)
ContentView.swift
class ElementHolder: ObservableObject {
@Published var elements: [String]
init(elements: [String]) {
self.elements = elements
}
}
struct ContentView: View {
@ObservedObject var elementHolder: ElementHolder
var body: some View {
VStack {
ForEach(self.elementHolder.elements.indices, id: \.self) { index in
SecondView(elementHolder: self.elementHolder, index: index)
}
}
}
}
struct SecondView: View {
@ObservedObject var elementHolder: ElementHolder
var index: Int
var body: some View {
HStack {
TextField("...", text: self.$elementHolder.elements[self.index])
Button(action: {
self.elementHolder.elements.remove(at: self.index)
}) {
Text("delete")
}
}
}
}
按下删除按钮时,应用程序因索引越界错误而崩溃。
有两个奇怪的事情,应用程序正在运行时
1)您删除VStack
并将ForEach
ContentView.swift
body
2)你把SecondView
的代码直接放到ForEach
只有一件事:我真的需要ObservableObject
,这段代码只是另一个代码的简化。
更新
我更新了我的代码并将Text
更改为TextField
,因为我不能只传递一个字符串,我需要双向连接。
问题出在单击删除按钮时执行更新的顺序。
按下按钮时,将发生以下情况:
elements
属性被改变objectWillChange
发布者发送通知,该发布者是ElementHolder
的一部分,由ObservableObject
协议声明。body
getter 更新其视图。body
getter 更新其视图。为了让代码不崩溃,3.1 必须在 3.2 之后执行。 尽管(据我所知)无法控制此顺序。
最优雅的解决方案是在 SecondView 中创建一个onDelete
闭包,它将作为参数传递。
这也将解决元素视图可以访问所有元素的架构反模式,而不仅仅是它显示的元素。
整合所有这些将产生以下代码:
struct ContentView: View {
@ObservedObject var elementHolder: ElementHolder
var body: some View {
VStack {
ForEach(self.elementHolder.elements.indices, id: \.self) { index in
SecondView(
element: self.elementHolder.elements[index],
onDelete: {self.elementHolder.elements.remove(at: index)}
)
}
}
}
}
struct SecondView: View {
var element: String
var onDelete: () -> ()
var body: some View {
HStack {
Text(element)
Button(action: onDelete) {
Text("delete")
}
}
}
}
有了这个,甚至可以删除 ElementHolder 并只拥有一个@State var elements: [String]
变量。
这是可能的解决方案 - 使SecondView
的主体不依赖于ObservableObject
。
用 Xcode 11.4 / iOS 13.4 测试 - 没有崩溃
struct SecondView: View {
@ObservedObject var elementHolder: ElementHolder
var index: Int
let value: String
init(elementHolder: ElementHolder, index: Int) {
self.elementHolder = elementHolder
self.index = index
self.value = elementHolder.elements[index]
}
var body: some View {
HStack {
Text(value) // not refreshed on delete
Button(action: {
self.elementHolder.elements.remove(at: self.index)
}) {
Text("delete")
}
}
}
}
另一种可能的解决方案是不要在SecondView
中观察ElementHolder
...不需要呈现和删除它 - 也不会崩溃
struct SecondView: View {
var elementHolder: ElementHolder // just reference
var index: Int
var body: some View {
HStack {
Text(self.elementHolder.elements[self.index])
Button(action: {
self.elementHolder.elements.remove(at: self.index)
}) {
Text("delete")
}
}
}
}
更新:文本字段的SecondView
变体(仅更改的是文本字段本身)
struct SecondViewA: View {
var elementHolder: ElementHolder
var index: Int
var body: some View {
HStack {
TextField("", text: Binding(get: { self.elementHolder.elements[self.index] },
set: { self.elementHolder.elements[self.index] = $0 } ))
Button(action: {
self.elementHolder.elements.remove(at: self.index)
}) {
Text("delete")
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.