Using Bindable with @Observable
model in EnvironmentValues
Using the new @Observable
macro in iOS 17 makes it easier to work with state changes in observed objects. The macro autogenerates code during compile time to add observation support to models. This works great when using the model as @State
variable.
struct Item: Hashable, Identifiable {
let id = UUID()
var name: String = "Some Name"
}
@Observable struct ItemList {
var items: [Item] = []
var selectedItems: Set<Item> = []
}
struct ListView: View {
@State var itemList = ItemList()
var body: some View {
List(selection: $itemList.selectedItems) {
ForEach(itemList.items) { item in
Text(item.name)
}
}
}
}
This works fine and as expected. But if the itemList
isn't a @State
variable but an Environment variable we get an error.
struct ListView: View {
@Environment(\.itemList) var itemList
var body: some View {
List(selection: $itemList.selectedItems) { // Error: Cannot find '$itemList' in scope
ForEach(itemList.items) { item in
Text(item.name)
}
}
}
}
The trick here is to use a @Bindable
property wrapper on itemList
.
struct ListView: View {
@Environment(\.itemList) var itemList
var body: some View {
@Bindable var bindableList = itemList // <-- here we use the property wrapper on our observed environment object
List(selection: $bindableList.selectedItems) { // <-- now we can use the bindable version of it
ForEach(itemList.items) { item in
Text(item.name)
}
}
}
}