SwiftUI - Интересная проблема с привязкой массива в списке при удалении - PullRequest
0 голосов
/ 22 января 2020

Это очень похоже на проблему, с которой я сталкивался ранее (на которую никто не мог ответить). Я пытаюсь создать динамический список c, в котором я могу редактировать элементы. Насколько я понимаю, рекомендуемый способ сделать это - иметь EditView с привязками, который активируется с помощью 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
                                $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{
                            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 не возникает на реальном устройстве.
Кажется, проблема с 'последним удалением элемента' делать с активной привязкой, присутствующей в представлении элемента.

1 Ответ

0 голосов
/ 23 января 2020

Хорошо, несмотря на мой комментарий, я попытался найти решение, и я мог бы найти приемлемое:

Мне пришлось переделать Person ... Конечно, вопрос был в целом, но я не мог Точно выяснить, когда что произойдет. Я даже попытался с локальным @State, который обновляет представление, а затем обновляет массив @ObservedObject ...

. Вот некоторые ссылки, которые могут помочь в дальнейшем исследовании, хотя ...

Детали Swift UI удалить

Как установить состояние переключения в foreach l oop в SwiftUI

Также эта ссылка здесь показывает, как в общем случае обновлять элементы наблюдаемого массива, что довольно круто!

{ ссылка }

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
                                $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
                        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)
    }
}

Надеюсь, это поможет!

...