How to bind an array and List if the array is a member of ObservableObject?

前端 未结 1 338

I want to create MyViewModel which gets data from network and then updates the arrray of results. MyView should subscribe to the $model.results

相关标签:
1条回答
  • 2021-01-26 21:41

    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.

    1. If the element is a struct or class, you can make it conform to the Identifiable protocol by adding a property var id: Hashable. You don't need the id parameter in this case.
    2. The other option is to specifically tell 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:

    • A Toggle needs to update a Bool value in response to a switch
    • A Slider needs to update a Double value in response to a slide
    • A TextField needs to update a String value in response to typing

    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.

    0 讨论(0)
提交回复
热议问题