Thorsten Stark

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)
            }
        }
    }
}
Tagged with: