I want to create MyViewModel
which gets data from network and then updates the arrray of results. MyView
should subscribe to the $model.results
The fix
Change your ForEach
block to
ForEach(model.results, id: \.self) { text in
Text(text)
}
Explanation
SwiftUI's error messages aren't doing you any favors here. The real error message (which you will see if you change Text(text)
to Text(text as String)
and remove the $
before model.results
), is "Generic parameter 'ID' could not be inferred".
In other words, to use ForEach
, the elements that you are iterating over need to be uniquely identified in one of two ways.
var id: Hashable
. You don't need the id
parameter in this case.ForEach
what to use as a unique identifier using the id
parameter. Update: It is up to you to guarentee that your collection does not have duplicate elements. If two elements have the same ID, any change made to one view (like an offset) will happen to both views.In this case, we chose option 2 and told ForEach
to use the String element itself as the identifier (\.self
). We can do this since String conforms to the Hashable protocol.
What about the $
?
Most views in SwiftUI only take your app's state and lay out their appearance based on it. In this example, the Text views simply take the information stored in the model and display it. But some views need to be able to reach back and modify your app's state in response to the user:
The way we identify that there should be this two-way communication between app state and a view is by using a Binding<SomeType>
. So a Toggle requires you to pass it a Binding<Bool>
, a Slider requires a Binding<Double>
, and a TextField requires a Binding<String>
.
This is where the @State
property wrapper (or @Published
inside of an @ObservedObject
) come in. That property wrapper "wraps" the value it contains in a Binding
(along with some other stuff to guarantee SwiftUI knows to update the views when the value changes). If we need to get the value, we can simply refer to myVariable
, but if we need the binding, we can use the shorthand $myVariable
.
So, in this case, your original code contained ForEach($model.results)
. In other words, you were telling the compiler, "Iterate over this Binding<[String]>
", but Binding
is not a collection you can iterate over. Removing the $
says, "Iterate over this [String]," and Array is a collection you can iterate over.