SwiftUI - 删除绑定数组中的元素会导致错误
SwiftUI - deletion of elements in Binding Array causes errors
我正在开发一个应用程序(使用 Xcode 11.3.1,目标设备:iPad)供我们公司的工程师报告他们所做的工作。应用程序的一部分需要是他们使用过的部件的可编辑列表。
我已经在一个简单的 'Person List' 测试项目(下面的完整项目代码)中复制了我试图实现的机制(Observed Object/@Binding 等)。
我仍在努力学习 SWiftUI,所以我可能在我的代码中做了一些愚蠢的事情。
这里的objective是创建一个可编辑字段的动态列表。预览代码时,它似乎可以完美运行,但是,删除元素后,事情就开始出错了。 (删除最后一个元素会导致“致命错误:索引超出范围”。
如果在删除一些元素后添加新元素,新元素的文本字段为空白且不可编辑。
如果有人能提供任何帮助,我将不胜感激。
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
[=10=].name2 < .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)
}
}
感谢 KRJW 和 Natalia Panferova 在本代码的各个方面提供的帮助。现在没有 'index out of range' 错误,也可以删除行而不会导致添加项目出现问题。
我分享这个答案是因为我相信这是创建可编辑列表的一种非常有用的机制。
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
[=10=].name2 < .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)
}
}
为方便起见,代码全部捆绑在一起。随意复制和粘贴并玩耍。
我正在开发一个应用程序(使用 Xcode 11.3.1,目标设备:iPad)供我们公司的工程师报告他们所做的工作。应用程序的一部分需要是他们使用过的部件的可编辑列表。
我已经在一个简单的 'Person List' 测试项目(下面的完整项目代码)中复制了我试图实现的机制(Observed Object/@Binding 等)。
我仍在努力学习 SWiftUI,所以我可能在我的代码中做了一些愚蠢的事情。
这里的objective是创建一个可编辑字段的动态列表。预览代码时,它似乎可以完美运行,但是,删除元素后,事情就开始出错了。 (删除最后一个元素会导致“致命错误:索引超出范围”。
如果在删除一些元素后添加新元素,新元素的文本字段为空白且不可编辑。
如果有人能提供任何帮助,我将不胜感激。
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
[=10=].name2 < .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)
}
}
感谢 KRJW 和 Natalia Panferova 在本代码的各个方面提供的帮助。现在没有 'index out of range' 错误,也可以删除行而不会导致添加项目出现问题。 我分享这个答案是因为我相信这是创建可编辑列表的一种非常有用的机制。
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
[=10=].name2 < .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)
}
}
为方便起见,代码全部捆绑在一起。随意复制和粘贴并玩耍。