SwiftUI - 删除时列表中绑定数组的有趣问题
SwiftUI - Interesting problem with binding array in list when deleting
这个问题和我以前遇到的问题非常相似(没有人能回答)。我正在尝试创建一个动态列表,我可以在其中编辑元素。据我所知,推荐的方法是拥有一个带有绑定的 EditView,它由 LIst 中的 NavigationLink 激活。
所以,我已经做到了。起初它似乎可以工作,直到我意识到每个 NavigationLink 只能工作一次(这是一个错误吗?)。我想不出是我做错了什么导致的。
然后我想也许我可以通过在列表中使用 EditView 来切换到就地编辑。我设计了一种理论上的方法来做到这一点,然后在我的代码中进行了尝试。起初它似乎工作得很好。但是,如果 'edit in place' 处于打开状态,则删除最后一个元素会导致 'Fatal error: Index out of range'.
我已将我的整个代码打包到一个文件中,因此您只需复制并粘贴到 Xcode 中即可自己尝试。
我开始认为也许 XCode 11.3.1 离完成的文章还很远。
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:200)
.font(.headline)
.padding(.all, 3)
.overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.blue, lineWidth: 1))
}.navigationBarTitle("Edit entry")
}
}
struct Person:Identifiable, Equatable{
var id:UUID
var name1:String
var name2:String
var isEditable:Bool
}
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
@State private var allowEditing:Bool = false
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","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.individuals.append(Person(id: UUID(), name1: self.edName1, name2: self.edName2, isEditable: false))
self.edName1 = ""
self.edName2 = ""
print("Element count: \(self.elementCount)")
}
Toggle(isOn: $allowEditing){Text("edit in place")}.padding(.all,5).overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color.red, lineWidth: 2))
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{
if self.allowEditing{
//Toggle to edit in place
Toggle(isOn: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!].isEditable){
Text("edit").font(.headline).foregroundColor(.green).opacity(individual.isEditable ? 1.0 : 0.4)
}.frame(width:100)
}
if individual.isEditable{
EditView(person: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!])
}
else{
NavigationLink(destination:EditView(person: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!])){
Text("\(individual.name1) \(individual.name2)")
.frame(width: 200, alignment: .leading)
.padding(.all, 3)
}// link
}
}
}.onDelete(perform: deleteRow)
}
}.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' 指出单一使用 NavLink 问题不会发生在真实设备上。
'last element delete' 问题似乎与元素视图中存在的活动绑定有关。
好吧,尽管我发表了评论,但我试图找到一个解决方案,我可能会找到一个可以接受的解决方案:
我不得不改造 Person
... 整个索引当然是问题,但我无法确切地知道什么时候会发生。我什至尝试使用本地 @State
更新视图,然后更新 @ObservedObject
...
的数组
这里有一些 link 可以帮助进一步调查,但...
Swift UI detail remove
这里的 link 还展示了如何更新观察到的数组的成员,这非常酷!:
struct EditView: View {
@ObservedObject var person: Person
var body: some View {
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))
}.navigationBarTitle("Edit entry")
}
}
struct RowView: View {
@Binding var allowEditing: Bool
@ObservedObject var individual: Person
var body: some View {
HStack {
if self.allowEditing {
//Toggle to edit in place
Toggle(isOn: self.$individual.isEditable){
Text("edit").font(.headline).foregroundColor(.green).opacity(self.individual.isEditable ? 1.0 : 0.4)
}//.frame(width:100)
}
if self.individual.isEditable{
EditView(person: self.individual)
}
else{
NavigationLink(destination:EditView(person: self.individual)){
Text("\(self.individual.name1) \(self.individual.name2)")
//.frame(width: 200, alignment: .leading)
.padding(.all, 3)
}// link
}
}
}
}
class Person: ObservableObject, Identifiable {
@Published var id:UUID
@Published var name1:String
@Published var name2:String
@Published var isEditable:Bool
init(id: UUID, name1: String, name2: String, isEditable: Bool){
self.id = id
self.name1 = name1
self.name2 = name2
self.isEditable = isEditable
}
}
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
@State private var allowEditing:Bool = false
// 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)")
}
Toggle(isOn: $allowEditing){Text("edit in place")}.padding(.all,5).overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color.red, lineWidth: 2))
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
RowView(allowEditing: self.$allowEditing, individual: 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)
}
}
希望对您有所帮助!
这个问题和我以前遇到的问题非常相似(没有人能回答)。我正在尝试创建一个动态列表,我可以在其中编辑元素。据我所知,推荐的方法是拥有一个带有绑定的 EditView,它由 LIst 中的 NavigationLink 激活。
所以,我已经做到了。起初它似乎可以工作,直到我意识到每个 NavigationLink 只能工作一次(这是一个错误吗?)。我想不出是我做错了什么导致的。
然后我想也许我可以通过在列表中使用 EditView 来切换到就地编辑。我设计了一种理论上的方法来做到这一点,然后在我的代码中进行了尝试。起初它似乎工作得很好。但是,如果 'edit in place' 处于打开状态,则删除最后一个元素会导致 'Fatal error: Index out of range'.
我已将我的整个代码打包到一个文件中,因此您只需复制并粘贴到 Xcode 中即可自己尝试。
我开始认为也许 XCode 11.3.1 离完成的文章还很远。
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:200)
.font(.headline)
.padding(.all, 3)
.overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.blue, lineWidth: 1))
}.navigationBarTitle("Edit entry")
}
}
struct Person:Identifiable, Equatable{
var id:UUID
var name1:String
var name2:String
var isEditable:Bool
}
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
@State private var allowEditing:Bool = false
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","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.individuals.append(Person(id: UUID(), name1: self.edName1, name2: self.edName2, isEditable: false))
self.edName1 = ""
self.edName2 = ""
print("Element count: \(self.elementCount)")
}
Toggle(isOn: $allowEditing){Text("edit in place")}.padding(.all,5).overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color.red, lineWidth: 2))
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{
if self.allowEditing{
//Toggle to edit in place
Toggle(isOn: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!].isEditable){
Text("edit").font(.headline).foregroundColor(.green).opacity(individual.isEditable ? 1.0 : 0.4)
}.frame(width:100)
}
if individual.isEditable{
EditView(person: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!])
}
else{
NavigationLink(destination:EditView(person: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!])){
Text("\(individual.name1) \(individual.name2)")
.frame(width: 200, alignment: .leading)
.padding(.all, 3)
}// link
}
}
}.onDelete(perform: deleteRow)
}
}.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' 指出单一使用 NavLink 问题不会发生在真实设备上。
'last element delete' 问题似乎与元素视图中存在的活动绑定有关。
好吧,尽管我发表了评论,但我试图找到一个解决方案,我可能会找到一个可以接受的解决方案:
我不得不改造 Person
... 整个索引当然是问题,但我无法确切地知道什么时候会发生。我什至尝试使用本地 @State
更新视图,然后更新 @ObservedObject
...
这里有一些 link 可以帮助进一步调查,但...
Swift UI detail remove
这里的 link 还展示了如何更新观察到的数组的成员,这非常酷!:
struct EditView: View {
@ObservedObject var person: Person
var body: some View {
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))
}.navigationBarTitle("Edit entry")
}
}
struct RowView: View {
@Binding var allowEditing: Bool
@ObservedObject var individual: Person
var body: some View {
HStack {
if self.allowEditing {
//Toggle to edit in place
Toggle(isOn: self.$individual.isEditable){
Text("edit").font(.headline).foregroundColor(.green).opacity(self.individual.isEditable ? 1.0 : 0.4)
}//.frame(width:100)
}
if self.individual.isEditable{
EditView(person: self.individual)
}
else{
NavigationLink(destination:EditView(person: self.individual)){
Text("\(self.individual.name1) \(self.individual.name2)")
//.frame(width: 200, alignment: .leading)
.padding(.all, 3)
}// link
}
}
}
}
class Person: ObservableObject, Identifiable {
@Published var id:UUID
@Published var name1:String
@Published var name2:String
@Published var isEditable:Bool
init(id: UUID, name1: String, name2: String, isEditable: Bool){
self.id = id
self.name1 = name1
self.name2 = name2
self.isEditable = isEditable
}
}
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
@State private var allowEditing:Bool = false
// 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)")
}
Toggle(isOn: $allowEditing){Text("edit in place")}.padding(.all,5).overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color.red, lineWidth: 2))
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
RowView(allowEditing: self.$allowEditing, individual: 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)
}
}
希望对您有所帮助!