This is a very similar problem to one I had before (which no one could answer). I'm trying to create a dynamic list in which I can edit elements. As far as I can gather, the recommended way to do this is to have an EditView, with bindings, that's activated by a NavigationLink in the LIst.
So, I've done that. It appears to work at first, until I realised that each NavigationLink would only work once (is this a bug?). I can't think what I could have done wrong to cause that.
Then I thought perhaps I can switch to in-place editing by having the EditView in the List. I devised a theoretical way to do this, then tried it in my code. And at first it seemed to work great. However, if 'edit in place' is on, deleting the last element causes 'Fatal error: Index out of range'.
I've bundled my whole code into one file so you can just copy and paste into Xcode to try for yourself.
I'm starting to think that maybe XCode 11.3.1 is far from the finished article, yet.
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)
}
}
Can anyone shed any light on this? I can't find anything to help me.
UPDATE: Thanks to 'krjw' for pointing out the single use NavLink problem does not happen on a real device.
The 'last element delete' issue seems to be something to do with an active binding being present in the element's view.