SwiftUI:无法删除列表中的行

Posted

技术标签:

【中文标题】SwiftUI:无法删除列表中的行【英文标题】:SwiftUI: cannot delete Row in List 【发布时间】:2020-05-23 10:20:14 【问题描述】:

我在 Xcode 中有一个小的 swiftUI 程序,它让我可以在一个列表中创建和删除用户,并使用步进器来计算用户的点数。

除非删除用户,否则一切正常(添加用户、重命名用户、步进计数)。

它会抛出一个错误:

致命错误:索引超出范围:文件 /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift, 第 444 行 2020-05-23 12:06:22.854920+0200 计数器[21328:1125981] 致命 错误:索引超出范围:文件 /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift, 第 444 行

代码如下:

import SwiftUI

struct ContentView : View 
    @State var isEditing = false
    @State var stepperWerte = [3, 5, 7, 9]
    @State var editText = ["Player 1", "Player 2", "Player 3", "Player 4"]

    var startName = "new Player"
    var startLeben = 5

    var body: some View 
        NavigationView 
                List()  
                    ForEach(0..<editText.count, id: \.self) 
                        spieler in HStack 
                            if self.editText.indices.contains(spieler) 
                            Stepper(value: self.$stepperWerte[spieler], in: -1...10, step: 1, label: 
                                TextField("", text: self.$editText[spieler], onEditingChanged: _ in , onCommit: self.saveText(id: spieler, Text: self.editText[spieler]) )
                                .layoutPriority(1)
                                .fixedSize(horizontal: true, vertical: false)
                                Text("\(self.stepperWerte[spieler]) - \(spieler) - \(self.editText.count)"))
                            
                        
                    
                    .onDelete(perform: spielerLoeschen)
                    .frame(width: nil, height: nil, alignment: .trailing)
                
            .navigationBarTitle(Text("Nav_Title"))
            .navigationBarItems(leading: Button(action:  self.isEditing.toggle() )  Text(isEditing ? "Done" : "Edit").frame(width: 85, height: 40, alignment: .leading) , 
                                trailing: Button(action: spielerHinzufuegen, label:  Image(systemName: "plus") ) )
            .environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
        
    

    func spielerLoeschen(at offsets: IndexSet) 
        stepperWerte.remove(atOffsets: offsets)
        editText.remove(atOffsets: offsets)
    
    func spielerHinzufuegen() 
        stepperWerte.append(startLeben)
        editText.append(startName)
    
    func saveText(id: Int, Text: String) 
        editText[id] = Text
    


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    

(忽略HStack后面的“if”,它没有实际效果,最后一个Text中的那些额外打印显示索引和总数)

如果我转储数组(stepperWerte 和 editText),它们会以正确的方式删除 -> 选择删除的玩家将从两个数组中正确删除。

如果我改变了

TextField("", text: self.$editText[spieler]

TextField("", text: self.$editText[0]

它可以工作(除非它自然地显示所有行中的第一个玩家,并且在删除所有玩家(=rows)后我得到了同样的错误)

任何帮助都会很棒 - 谢谢!

【问题讨论】:

问题解释及解决方法见here 【参考方案1】:

根据@Asperi,我已将代码更改为以下内容: 导入 SwiftUI

struct BetterTextField : View 
    var container: Binding<[String]>
    var index: Int
    @State var text: String

    var body: some View 
        TextField("", text: self.$text, onCommit: 
            self.container.wrappedValue[self.index] = self.text
        )
        .layoutPriority(1)
        .fixedSize(horizontal: true, vertical: false)
    


struct ContentView : View 
    @State var isEditing = false
    @State var stepperWerte = [3, 5, 7, 9]
    @State var editText = ["Player 1", "Player 2", "Player 3", "Player 4"]

    var startName = "new Player"
    var startLeben = 5

    var body: some View 
        NavigationView 
                List()  
                    ForEach(0..<editText.count, id: \.self) 
                        spieler in HStack 
                            if self.editText.indices.contains(spieler) 
                            Stepper(value: self.$stepperWerte[spieler], in: -1...10, step: 1, label: 
                                BetterTextField(container: self.$editText, index: self.editText.firstIndex(of: self.editText[spieler])!, text: self.editText[spieler])
                                Text("\(self.stepperWerte[spieler]) - \(spieler) - \(self.editText.count)"))
                            
                        
                    
                    .onDelete(perform: spielerLoeschen)
                    .frame(width: nil, height: nil, alignment: .trailing)
                
            .navigationBarTitle(Text("Nav_Title"))
            .navigationBarItems(leading: Button(action:  self.isEditing.toggle() )  Text(isEditing ? "Done" : "Edit").frame(width: 85, height: 40, alignment: .leading) , 
                                trailing: Button(action: spielerHinzufuegen, label:  Image(systemName: "plus") ) )
            .environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
        
    

    func spielerLoeschen(at offsets: IndexSet) 
        stepperWerte.remove(atOffsets: offsets)
        editText.remove(atOffsets: offsets)
    
    func spielerHinzufuegen() 
        stepperWerte.append(startLeben)
        editText.append(startName)
    
    func saveText(id: Int, Text: String) 
        editText[id] = Text
    


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    

...它有效 - 谢谢!

但是: 这是 SwiftUI 中的错误还是故意的?

【讨论】:

【参考方案2】:

问题是您没有直接在 ForEach 循环中使用您的项目。考虑将数据结构用作对象并使其可识别。

struct Player : Identifiable 
    let id = UUID()
    var stepperWerte: Int
    var editText : String


struct ContentView : View 
    @State var isEditing = false

    @State var players = [Player(stepperWerte: 3, editText: "Player 1"), Player(stepperWerte: 5, editText: "Player 2"), Player(stepperWerte: 7, editText: "Player 3"), Player(stepperWerte: 9, editText: "Player 4")]

    var startName = "new Player"
    var startLeben = 5

    var body: some View 
        NavigationView 
                List() 

                    ForEach(self.players)  player in
                        SecondView(player: player)
                    
                    .onDelete(perform: spielerLoeschen)
                
                .frame(width: nil, height: nil, alignment: .trailing)
                .navigationBarTitle(Text("Nav_Title"))
                .navigationBarItems(leading: Button(action:  self.isEditing.toggle() )  Text(isEditing ? "Done" : "Edit").frame(width: 85, height: 40, alignment: .leading) ,
                                trailing: Button(action: spielerHinzufuegen, label:  Image(systemName: "plus") ) )
            .environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
        
    

    func spielerLoeschen(at offsets: IndexSet) 
        players.remove(atOffsets: offsets)
    
    func spielerHinzufuegen() 
        players.insert(Player(stepperWerte: 4, editText: "Neuer Player"), at: 0)
    


struct SecondView : View 

    var player : Player

    @State var stepperWerte : Int
    @State var name : String

    init(player : Player)
    
        self._stepperWerte = State(initialValue: player.stepperWerte)
        self._name = State(initialValue: player.editText)

        self.player = player
    


    var body: some View
    
        Stepper(value: self.$stepperWerte, in: -1...10, step: 1, label: 
            TextField("", text: self.$name)
            .layoutPriority(1)
            .fixedSize(horizontal: true, vertical: false)
            Text("\(player.stepperWerte)")
            )
    


我创建了一个结构Player,然后创建了一个包含许多玩家的数组。在 ForEach 中,您可以直接使用 players 作为 Player 确认到 Identifiable 协议。这更容易,因为您可以在 ForEach 循环中访问播放器对象,而不必使用索引访问所有内容。在删除函数中,您只需从数组中删除对象或添加新对象。现在删除工作正常。

我从列表行中删除了一些代码,只是为了更容易重现它,如果您想知道的话。

【讨论】:

这也很好,我选择了这个,因为它对我来说更容易理解。谢谢! 不知何故,玩家名称的编辑和步进器的值没有改变/保存。这在您的版本中有效吗? @ma2412 我已经更新了代码。添加作品,并更改名称。我将它外包给第二个视图并更改了结构。 Sollte funktionieren ;)

以上是关于SwiftUI:无法删除列表中的行的主要内容,如果未能解决你的问题,请参考以下文章

如果列表行在 SwiftUI 中包含文本字段,则无法从 foreach 中删除元素

SwiftUI:Firebase ForEach 列表执行编辑功能无法编辑选定的行项目

如何从 Mac OS 中的 SwiftUI 列表中删除底部/顶部项目填充

SwiftUI 试图从列表中删除对象但无法在不可变值上使用变异成员:是一个“让”常量

无法使用 SwiftUI 从 Firebase / Firestore 中删除文档

SwiftUI 无法删除选取器上方的空间 - 表单版本