SwiftUI - 删除绑定数组中的元素会导致错误

Posted

技术标签:

【中文标题】SwiftUI - 删除绑定数组中的元素会导致错误【英文标题】:SwiftUI - deletion of elements in Binding Array causes errors 【发布时间】:2020-01-15 11:35:02 【问题描述】:

我正在开发一个应用程序(使用 Xcode 11.3.1,目标设备:iPad),供我们公司的工程师报告他们所做的工作。应用程序的一部分需要是他们使用过的部分的可编辑列表。

我已经在一个简单的“人员列表”测试项目(下面的完整项目代码)中复制了我试图实现的机制(观察对象/@Binding 等)。

我仍在努力学习 SWiftUI,所以我可能在我的代码中做了一些愚蠢的事情。

这里的目标是创建一个带有可编辑字段的动态列表。预览代码时,它似乎可以完美运行,但是,在删除元素后,事情就开始出错了。 (删除最后一个元素会导致“致命错误:索引超出范围”。

如果在删除某些元素后添加新元素,则新元素的文本字段为空白且不可编辑。

我非常感谢任何人可以提供的任何帮助。

import SwiftUI

struct EditView: View 
    @Binding var person:Person
    var body: some View 
        HStack
            Group
                TextField("name1", text: $person.name1)
                TextField("name2", text: $person.name2)
            .frame(width:150)
            .font(.headline)
                .padding(.all, 3)
                .overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.blue, lineWidth: 1))
        
    

struct Person:Identifiable, Equatable
    var id:UUID
    var name1:String
    var name2:String

class PersonList: ObservableObject 
    @Published var individuals = [Person]()// Array of Person structs

struct ContentView: View 
    @ObservedObject var people = PersonList()// people.individuals = [Person] array
    @State private var edName1:String = "" //temporary storage for adding new member
    @State private var edName2:String = "" //temporary storage for adding new member
    var elementCount:Int
        let c = people.individuals.count
        return c
    
    // arrays for testing - adds random names from these (if input field '1st name' is empty)...
    var firstNames = ["Nick","Hermes","John","Hattie","Nicola","Alan", "Dwight", "Richard"]
    var surnames = ["Fury","Smith","Jones","Hargreaves","Bennylinch", "Davidson","Lucas","Partridge"]
    var body: some View 
        NavigationView
            VStack
                HStack
                    Text("Add person:")
                        .padding(.all, 5)
                        .frame(alignment: .leading)
                    TextField("1st name", text: $edName1)
                        .frame(width:150)
                        .padding(.all, 5)
                        .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.blue, lineWidth: 2))
                    TextField("2nd name", text: $edName2)
                        .frame(width:150)
                        .padding(.all, 5)
                        .overlay(RoundedRectangle(cornerRadius: 8)
                            .stroke(Color.blue, lineWidth: 2))
                    // ???? Button...
                    Image(systemName: "plus.square")
                        .font(.title)
                        .foregroundColor(.orange)
                        .onTapGesture 
                            if self.edName1 == ""
                                self.edName1 = self.firstNames.randomElement() ?? "⁉️"
                                self.edName2 = self.surnames.randomElement() ?? "⁉️"
                            
                            self.people.individuals.append(Person(id: UUID(), name1: self.edName1, name2: self.edName2))
                            self.edName1 = ""
                            self.edName2 = ""
                            print("Element count: \(self.elementCount)")
                    
                    Spacer()
                    // ???? Button...sort
                    Image(systemName: "arrow.up.arrow.down.square")
                        .font(.title)
                        .padding(.all,4)
                        .foregroundColor(.blue)
                        .onTapGesture 
                            self.people.individuals.sort // sort list alphabetically by name2
                                $0.name2 < $1.name2
                            
                    
                    // ???? Button...reverse order
                    Image(systemName: "arrow.uturn.up.square")
                        .font(.title)
                        .padding(.all,8)
                        .foregroundColor(.blue)
                        .onTapGesture 
                            self.people.individuals.reverse()
                    
                .padding(.all,8)
                    .overlay(RoundedRectangle(cornerRadius: 12)
                        .stroke(Color.orange, lineWidth: 2))
                List
                    ForEach(people.individuals)individual in
                        HStack
                          EditView(person: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!])

                            Text("\(individual.name1) \(individual.name2)")
                                .frame(width: 200, alignment: .leading)
                                .padding(.all, 3)
                            Text("\(self.people.individuals.firstIndex(of: individual)!)")
                            Spacer()

                            Image(systemName: "xmark.circle.fill").font(.title).foregroundColor(.red)//❌
                                .onTapGesture 
                                    let deletedElement = self.people.individuals.remove(at: self.people.individuals.firstIndex(of: individual)!)
                                    print("Deleted element:\(deletedElement) Element count: \(self.elementCount)")
                            
                        
                    //.onDelete(perform: deleteRow)// an alternative to the red xmark circle
                
            .navigationBarTitle("People List (\(elementCount))")
        .navigationViewStyle(StackNavigationViewStyle())
    
    func deleteRow(at offsets: IndexSet)
        self.people.individuals.remove(atOffsets: offsets)
    




struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
          .environment(\.colorScheme, .dark)
    

【问题讨论】:

提示:你的问题在这里:EditView(member: self.$people.members[self.people.members.firstIndex(of: member)!]) @Chris - 感谢您的提示。我会看看我是否能解决我做错了什么。 稍微重构一下代码后,上面@Chris 所指的行现在是; EditView(person: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!]) 。我仍在努力找出我的代码有什么问题。诚然,目前我对 SwiftUI 的了解还很匮乏。 我已经尝试了对这行代码的各种修改,但都无法编译 我最初的想法是将这一行写成:EditView(person: individual),但这会导致代码中更高的编译错误。我认为是因为它没有将绑定传递给子视图。似乎需要 - EditView(person: Binding) - 这是我在上面的示例中尝试过的。这是我可以在那里获得“$”绑定的唯一方法。 【参考方案1】:

感谢 KRJW 和 Natalia Panferova 在此代码的不同方面提供帮助。现在没有“索引超出范围”错误,并且可以删除行而不会导致添加项目出现问题。 我分享这个答案是因为我相信它是创建可编辑列表的一种非常有用的机制。

import SwiftUI

class Person: ObservableObject, Identifiable, Equatable 
    static func == (lhs: Person, rhs: Person) -> Bool 
        return lhs.id == rhs.id
    

    @Published var id:UUID
    @Published var name1:String
    @Published var name2:String

    init(id: UUID, name1: String, name2: String, isEditable: Bool)
        self.id = id
        self.name1 = name1
        self.name2 = name2
    



struct EditView: View 
    @ObservedObject var person: Person
    var body: some View 
        VStack
            HStack
                Group
                    TextField("name1", text: $person.name1)
                    TextField("name2", text: $person.name2)
                //.frame(width:200)
                    .font(.headline)
                    .padding(.all, 3)
                    .overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.blue, lineWidth: 1))
            .id(person.id)
        
    





struct ContentView: View 
    @State var people = [Person]()//try! ObservableArray<Person>(array: []).observeChildrenChanges(Person.self)// people.individuals = [Person] array

    @State private var edName1:String = "" //temporary storage for adding new member
    @State private var edName2:String = "" //temporary storage for adding new member

    // arrays for testing - adds random names from these (if input field '1st name' is empty)...
    var firstNames = ["Nick","Hermes","John","Hattie","Nicola","Alan", "Dwight", "Richard","Turanga", "Don","Joey"]
    var surnames = ["Farnsworth","Fry","Wong","Zoidberg","Conrad","McDougal","Power","Clampazzo","Brannigan","Kroker","Leela"]

    var body: some View 
        NavigationView
            VStack
                HStack
                    Text("Add person:")
                        .padding(.all, 5)
                        .frame(alignment: .leading)
                    TextField("1st name", text: $edName1)
                        //.frame(width:150)
                        .padding(.all, 5)
                        .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.blue, lineWidth: 2))
                    TextField("2nd name", text: $edName2)
                        //.frame(width:150)
                        .padding(.all, 5)
                        .overlay(RoundedRectangle(cornerRadius: 8)
                            .stroke(Color.blue, lineWidth: 2))
                    // ? Button...
                    Image(systemName: "plus.circle")
                        .font(.largeTitle)
                        .foregroundColor(.orange)
                        .onTapGesture 
                            if self.edName1 == ""
                                self.edName1 = self.firstNames.randomElement() ?? "⁉️"
                                self.edName2 = self.surnames.randomElement() ?? "⁉️"
                            
                            self.people.append(Person(id: UUID(), name1: self.edName1, name2: self.edName2, isEditable: false))
                            self.edName1 = ""
                            self.edName2 = ""
                            print("Element count: \(self.people.count)")
                    
                    Spacer()
                    // ? Button...sort
                    Image(systemName: "arrow.up.arrow.down.square")
                        .font(.title)
                        .padding(.all,4)
                        .foregroundColor(.blue)
                        .onTapGesture 
                            self.people.sort // sort list alphabetically by name2
                                $0.name2 < $1.name2
                            
                    
                    // ? Button...reverse order
                    Image(systemName: "arrow.uturn.up.square")
                        .font(.title)
                        .padding(.all,8)
                        .foregroundColor(.blue)
                        .onTapGesture 
                            self.people.reverse()
                    
                .padding(.all,8)
                    .overlay(RoundedRectangle(cornerRadius: 12)
                        .stroke(Color.orange, lineWidth: 2))



                List 
                    ForEach(self.people)  person in
                        EditView(person: person)
                    .onDelete(perform: deleteRow)
                



            .navigationBarTitle("People List (\(self.people.count))")
        .navigationViewStyle(StackNavigationViewStyle())
    

    func deleteRow(at offsets: IndexSet)
        self.people.remove(atOffsets: offsets)
        print(self.people.count)
    



struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
           // .environment(\.colorScheme, .dark)
    

为方便起见,所有代码都捆绑在一起。随意复制和粘贴和玩耍。

【讨论】:

以上是关于SwiftUI - 删除绑定数组中的元素会导致错误的主要内容,如果未能解决你的问题,请参考以下文章

从数组中删除 - 致命错误:索引超出范围 - SwiftUI 绑定

SwiftUI - 致命错误:从数组中删除元素时索引超出范围

SwiftUI:从 ForEach 中删除项目导致索引超出范围

SwiftUI:绑定数组的安全下标

如何将 SwiftUI 视图绑定到数组中元素的属性

SwiftUI 将数组的元素作为绑定传递给子视图