在ForEach中删除数组元素时,SwiftUI超出索引
我在这里看了不同的问题,但不幸的是我找不到答案。这是我的代码: SceneDelegate.swift在ForEach中删除数组元素时,SwiftUI超出索引,swift,swiftui,Swift,Swiftui,我在这里看了不同的问题,但不幸的是我找不到答案。这是我的代码: SceneDelegate.swift ... let contentView = ContentView(elementHolder: ElementHolder(elements: ["abc", "cde", "efg"])) ... window.rootViewController = UIHostingController(rootView: contentView) class ElementHolder: Obse
...
let contentView = ContentView(elementHolder: ElementHolder(elements: ["abc", "cde", "efg"]))
...
window.rootViewController = UIHostingController(rootView: contentView)
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")
}
}
}
}
ContentView.swift
...
let contentView = ContentView(elementHolder: ElementHolder(elements: ["abc", "cde", "efg"]))
...
window.rootViewController = UIHostingController(rootView: contentView)
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")
}
}
}
}
按delete(删除)按钮时,应用程序崩溃,出现索引越界错误
有两件奇怪的事情,应用程序运行时
1) 您删除VStack
,只需将ForEach
放入ContentView.swift的body
,或
2) 您可以将SecondView
的代码直接放入ForEach
只有一件事:我真的需要有observateObject
,这段代码只是另一段代码的简化
更新
我更新了我的代码并将Text
更改为TextField
,因为我不能只传递一个字符串,我需要双向连接。问题源于单击删除按钮时执行更新的顺序
按下按钮时,将发生以下情况:
元素持有者的元素
属性已更改
这将通过objectWillChange
发布者发送通知,该发布者是ElementHolder
的一部分,由observeObject
协议声明
订阅此发布服务器的视图将收到一条消息,并将更新其内容。
SecondView接收通知并通过执行body
getter更新其视图
ContentView接收通知并通过执行body
getter更新其视图
要使代码不崩溃,必须在3.2之后执行3.1。虽然(据我所知)不可能控制这个秩序
最优雅的解决方案是在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的主体不依赖observeObject
使用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
变体(只更改了文本字段本身)
谢谢,这清楚地说明了应用程序崩溃的原因:SecondView将首先得到通知(我试图设置断点,但我无法解决这个问题)。不幸的是,您的解决方案不是我要搜索的,因为我的SecondView
需要ElementHolder
的更多其他属性,所以我不能只传递一个字符串。。。这就是您的代码工作的原因,因为SecondView不包含ElementHolder。。。我有一个文本字段
,而不仅仅是一个文本
,请查看我的更新代码。您可以通过绑定传递每个SecondView表示的项目。绑定有两种工作方式,因此您将值从ContentView传递到SecondView,SecondView将更改传递回ContentView。删除仍然需要单独处理,如我的帖子所示。您可以使用绑定创建绑定(get:{self.elementHolder.elements[index]},set:{newValue in self.elementHolder.elements[index]=newValue})
。在第二个视图中,必须有一个变量@Binding-var-value
@Lupurus,您有幸运吗?我在这里也有同样的情况。列表包含元素,每个元素都应该通过视图绑定,以便能够在一个视图中更新它们并发布其他视图。它就像一个没有onDelete
@Faruk的符咒:如果与.index
一起使用,它就不起作用。只是不要使用.index
,您需要迭代元素本身,然后必须使用.firstIndex(其中:{$0.id==element.id})
,如果您需要每个元素的索引,谢谢!解决方案2很好,但我有一个文本字段,而不仅仅是一个文本;)(这就是简化代码的问题;))。解决方案1也有同样的问题:(我将更新我的代码示例。非常感谢!!除了用于删除的专用按钮之外,它是否与ForEach
的onDelete
修饰符
一起工作?@asperide这仍然有效?它在Xcode 12.5上对我来说崩溃了。。。