Populating SwiftUI List with array elements that can be editied in TextEditor

Viewed 20

I have a SwiftUI app that produces a List made from elements of an array of columns held in a struct.

I need the items in the row to be editable so I'm trying to use TextEditor but the bindings are proving difficult. I have a working prototype however the TextEditors are uneditable - I get the warning:

Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.

Here's a much shortened version of my code which produces the same problem:

import SwiftUI

struct Item: Identifiable {
    @State var stringValue: String
    var id: UUID = UUID()
}

struct ArrayContainer {
    var items: [Item] = [Item(stringValue: "one"), Item(stringValue: "two")]
}

struct ContentView: View {
    
    @State var wrapperArray: ArrayContainer = ArrayContainer()
    
    var body: some View {
        List {
            Section(header: Text("Test List")) {
                ForEach (Array(wrapperArray.items.enumerated()), id: \.offset) { index, item in
                    TextEditor(text: item.$stringValue)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

How can I bind the TextEditor to the items stringValues within the items array?

TIA.

1 Answers

@State should only be used as a property wrapper on your View -- not on your model.

You can use a binding within ForEach using the $ syntax to get an editable version of the item.

struct Item: Identifiable {
    var stringValue: String
    var id: UUID = UUID()
}

struct ArrayContainer {
    var items: [Item] = [Item(stringValue: "one"), Item(stringValue: "two")]
}

struct ContentView: View {
    
    @State var wrapperArray: ArrayContainer = ArrayContainer()
    
    var body: some View {
        List {
            Section(header: Text("Test List")) {
                ForEach ($wrapperArray.items, id: \.id) { $item in
                    TextEditor(text: $item.stringValue)
                }
            }
        }
    }
}

This could be simplified further to avoid the ArrayContainer if you want:

struct ContentView: View {
    
    @State var items: [Item] = [Item(stringValue: "one"), Item(stringValue: "two")]
    
    var body: some View {
        List {
            Section(header: Text("Test List")) {
                ForEach ($items, id: \.id) { $item in
                    TextEditor(text: $item.stringValue)
                }
            }
        }
    }
}
Related