Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在ForEach中删除数组元素时,SwiftUI超出索引_Swift_Swiftui - Fatal编程技术网

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

在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

我在这里看了不同的问题,但不幸的是我找不到答案。这是我的代码:

SceneDelegate.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")
            }
        }
    }

}
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上对我来说崩溃了。。。