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
Bindable
property 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
Binding
and pass that to the text field.TextField("", text: .init { controller.strings[r][c] } set: { controller.strings[r][c] = $0 })