Index out of range for TextField binding to an element in an array

I want to create a UI that allows me to create an arbitrary list of strings. For this, I have created a UI that has a button to append strings to a String Array. I also want to be able to arbitrarily remove any of the strings, so I have another button next to each element that removes it from the array. If I use a simple `Text` view, everything works just fine; however, if I change to using a `TextView` to allow the user to change the value, then I get an "index out of range" error when trying to remove an element from the array. Digging in it looks like although the element is removed from the array, the view acts as if it hasn't been removed. I'm not sure if I'm doing something wrong or if this is a bug.


To reproduce:

1. Create an iOS project with SwiftUI

2. Replace the default ContentView.swift code with the following:

import SwiftUI

struct ContentView : View {
  @State var myList: [String] = []

  var body: some View {
    VStack(alignment: .leading) {
      AppendButton(myList: $myList)
      ListView(myList: $myList)
      Spacer()
    }
  }
}

struct AppendButton: View {
  @Binding var myList: [String]

  var body: some View {
    HStack {
      Button(action: { self.myList.append("List Item") }) {
        Image(systemName: "plus")
          .padding()
        }
        .foregroundColor(.black)
        .background(Color(.sRGB, white: 0.84, opacity: 1))
        .cornerRadius(5.2)

      Text("Add Item")
      Spacer()
    }
  }
}

struct ListView: View {
  @Binding var myList: [String]

  var body: some View {
    VStack(alignment: .leading) {
      ForEach(0..<myList.count) { index in
        HStack {
          Button(action: { self.myList.remove(at: index) }) {
            Image(systemName: "minus")
              .padding()
            }
            .foregroundColor(.black)
            .background(Color(.sRGB, white: 0.84, opacity: 1))
            .cornerRadius(5.2)
          //Text("\(self.myList[index]) \(index)")
          TextField(self.$myList[index])
        }
        .padding(.leading)
      }
    }
    .padding(.leading)
  }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
#endif

3. Run the app in the simulator

4. Click the plus button next to "Add Item" once or a few times

5. Click the minus button next to a "List Item"


I expect it to remove the item, but the app will crash with the "index out of range" bug. If you comment out the "TextField" line and uncomment the corresponding "Text" field instead, the application works just fine. Again, by breaking various pieces out, I've determined that the "ForEach" view runs one-to-many times for some reason which results in the "index out of range". (The issue isn't that the index is out of range in the button action that removes the item — that succeeds just fine.)

Hi bfad,

I played around with this a littlebit and I think I found a work around for this. Instead of using @State for mylist, if you move your list to a separate class conforming to BindableObject and using to refer to that in the view using @Objectbinding it works fine. See my code below. My threory is that SwiftUI gets confused when you mutate the @State mylist because myList is an array and arrays are structs. I am guessing it has a problem detecting changes when structs are mutated, but it is only a guess.


//
//  ContentView.swift
//  ForumX
//
//  Created by sumuhan umamaheswarampillai on 11/07/2019.
//  Copyright © 2019 sumuhan umamaheswarampillai. All rights reserved.
//

import SwiftUI

struct ContentView : View {
    //@State var myList: [String] = []
    @ObjectBinding var myClass: MyClass
   
    var body: some View {
        VStack (alignment: .leading) {
            AppendButton(myClass: myClass)
            ListView(myClass: myClass)
            Spacer()
        }
    }
}

struct AppendButton: View {
    //@Binding var myList: [String]
    @ObjectBinding var myClass: MyClass
    var body: some View {
        HStack {
            Button(action: { self.myClass.myList.append("List Item") }) {
                Image(systemName: "plus")
                    .padding()
            }
            .foregroundColor(.black)
                .background(Color(.sRGB, white: 0.84, opacity: 1))
                .cornerRadius(5.2)
           
            Text("Add Item")
            Spacer()
        }
    }
}

struct ListView: View {
    //@Binding var myList: [String]
    @ObjectBinding var myClass: MyClass
    var body: some View {
        VStack(alignment: .leading) {
            ForEach(0..<myclass.mylist.count) {="" index="" in<br="">                HStack {
                    Button(action: { self.myClass.myList.remove(at: index) }) {
                        Image(systemName: "minus")
                            .padding()
                    }
                    .foregroundColor(.black)
                        .background(Color(.sRGB, white: 0.84, opacity: 1))
                        .cornerRadius(5.2)
                    //Text("\(self.myList[index]) \(index)")
                    TextField(self.$myClass.myList[index])
                }
                .padding(.leading)
            }
        }
        .padding(.leading)
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView(myClass: MyClass())
    }
}
#endif



And my class is


class MyClass: BindableObject {
    typealias PublisherType = PassthroughSubject<void,never>
   
    var myList: [String] = [] {
        didSet{
            didChange.send()
        }
    }
    var didChange = MyClass.PublisherType()
}



And remeber to update the ScenDelegate to pass in the dependency


iflet windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: ContentView(myClass: MyClass()))
            self.window = window
            window.makeKeyAndVisible()
        }
Index out of range for TextField binding to an element in an array
 
 
Q