3

I have a Subtask defined like this:

struct Subtask: Identifiable{
  var id = UUID().uuidString
  var text = ""
}

I populate an array of them in an ObservableObject like this:

//View Model
class SubtaskModel: ObservableObject {
  static let shared = SubtaskModel()

  @Published var subtasks = [Subtask]()
  
  init(){
    //Code that creates the subtasks array
  }
}

Then I render them starting in my parent view like this:

//Parent View
struct ParentView: View {
  @StateObject var model = SubtaskModel.shared
  ...
  
  ForEach($model.subtasks) { subtask in
    ChildView(subtask: $subtask)
  }
}

With the intent of being able to modify each individual Subtask in the child view:

//Child View
struct ChildView: View{
  @Binding var subtask: Subtask
  
  ...
  TextField("Text...", text: $subtask.text, onCommit:{
    print("Save subtask!")
  })
}

I'm getting an error in the ParentView on this line:

ChildView(subtask: $subtask)

...that says:

Cannot find '$subtask' in scope

So it seems the ForEach is not returning a binding for each of the subtasks returned by my view model. I have tried other variations of the ForEach but still get the error:

ForEach(model.subtasks) { subtask in
ForEach($model.subtasks, id: \.self) { subtask in
ForEach($model.subtasks, id: \.id) { subtask in

How can I bind a child Subtask from a parent to a child view?

Clifton Labrum
  • 13,053
  • 9
  • 65
  • 128

1 Answers1

3

You missed the $ before subtask.

If you don't write $subtask, then subtask is now a Binding<Subtask>. This would mean you can still do subtask.wrappedValue to use subtask normally and subtask to use the binding, but that's not as neat. With the $, $subtask is a Binding<Subtask> and subtask is a Subtask.

Change:

ForEach($model.subtasks) { subtask in
    ChildView(subtask: $subtask)
}

To:

ForEach($model.subtasks) { $subtask in
    ChildView(subtask: $subtask)
}

Or alternatively (and more confusingly, I don't recommend):

ForEach($model.subtasks) { subtask in
    ChildView(subtask: subtask)
}
George
  • 25,988
  • 10
  • 79
  • 133
  • Hmm... changing it to `$subtask` results in two new errors: `Generic struct 'ForEach' requires that 'Binding<[Subtask]>' conform to 'RandomAccessCollection'` and then `Unable to infer type of a closure parameter '$subtask' in the current context`. I've never seen a `$` on the iteration value in a `ForEach`. – Clifton Labrum Oct 06 '21 at 01:29
  • @CliftonLabrum It works in Xcode 13. It's called [element binding syntax](https://www.swiftbysundell.com/articles/bindable-swiftui-list-elements/#xcode-s-new-element-binding-syntax), which requires Xcode 13. Can be back-deployed to any previous iOS versions. – George Oct 06 '21 at 01:38
  • Oh, interesting. I’m using Xcode 13. Do I have to enable forward-looking features like this somehow? My app targets are iOS 14 and macOS 11. – Clifton Labrum Oct 06 '21 at 01:58
  • @CliftonLabrum Maybe the compiler is having troubles for you, does explicitly specifying the type with `{ ($subtask: Binding) in` work? – George Oct 06 '21 at 02:14
  • It's working (without the specified type) when I build it for iOS 15. It's just not working for the Mac version of my app, so I must have a separate issue. Thanks for your help! – Clifton Labrum Oct 06 '21 at 03:59