繁体   English   中英

删除 ForEach 中的数组元素时 SwiftUI 超出索引

[英]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 ,因为我不能只传递一个字符串,我需要双向连接。

问题出在单击删除按钮时执行更新的顺序。

按下按钮时,将发生以下情况:

  1. 元素持有者的elements属性被改变
  2. 这会通过objectWillChange发布者发送通知,该发布者是ElementHolder的一部分,由ObservableObject协议声明。
  3. 订阅此发布者的视图会收到一条消息,并将更新其内容。
    1. SecondView 接收通知并通过执行body getter 更新其视图。
    2. ContentView 接收通知并通过执行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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM