Code that reproduces the issue
import SwiftUI
@main
struct TextFieldsGridApp: App {
@State private var controller = Controller()
var body: some Scene {
WindowGroup {
GridView()
.environment(controller)
}
}
}
struct GridView: View {
@Environment(Controller.self) private var c
var body: some View {
VStack {
ForEach(0..<c.strings.count, id: \.self) { r in
HStack {
ForEach(0..<4, id: \.self) { c in
TextField(
"",
text: c.strings[r][c]
)
.textFieldStyle(.roundedBorder)
}
}
}
}
.padding()
}
}
#Preview {
GridView()
.environment(Controller())
}
@Observable
class Controller {
private(set) var strings: [[String]] = Array(
repeating: Array(repeating: "A", count: 4),
count: 4,
)
}
Error
Cannot convert value of type 'Range<Int>' to expected argument type 'Binding<C>', caused by ForEach(0..<4, id: \.self) { c in.
Which I do not get if I say, for example:
struct GridView: View {
@State private var text = "H"
// ...
TextField("", text: $text)
Environment
MacBook Air M1 8GB iOS Sequoia 15.5 Xcode 16.4 Preview: iPhone 16 Pro, iOS 18.5.
The issue is this part despite what the error message says:
TextField("", text: c.strings[r][c])
The first quick point is that you are using c here as the environment object and the ForEach closure parameter, so Swift is getting confused and doesn't know which one you are referring to. I will be referencing the environment object property as controller.
The second point is illustrated further by the example you gave.
TextField("", text: $text)
TextField expects to be passed a Binding<String> which is why you prepend the $ symbol to obtain one from a variable.
However in your code, you are passing in controller.strings[r][c] which is just a regular String and not a binding, which is why the error occurs.
To solve this, you need a way of getting a Binding<String> from your current String property.
There are two ways of approaching this — pick whichever one you prefer.
-
Use the
Bindableproperty wrapper which facilitates creating bindings from the mutable properties of observable objects.var body: some View { @Bindable var controller = controller TextField("", text: $controller.strings[r][c]) }
-
Manually create a
Bindingand pass that to the text field.TextField("", text: .init { controller.strings[r][c] } set: { controller.strings[r][c] = $0 })