Binding a var from struct array under an ObservableObject

Binding a var from struct array under an ObservableObject

I have an environment object Order, containing a list of OrderItem (aka an item + a quantity) I'm presenting a list with steppers so quantity can be modified for each item.

struct MenuItem: Codable, Hashable, Identifiable {
    var id: UUID
    var name: String
    var price: Int
}

struct OrderItem: Hashable, Identifiable {
    var item: MenuItem
    var quantity: Int
    var id: UUID
}

class Order: ObservableObject {   
    @Published var items = [OrderItem]()
}

struct OrderView: View {
    @EnvironmentObject var order: Order
    
    var body: some View {
        List {
            Section("My Order") {
                ForEach($order.items) { $orderItem in
                    VStack {
                        Text(orderItem.item.name)
                        HStack {
                            Text("Quantity: \(orderItem.quantity)")
                            Spacer()
                            Stepper("", value: $orderItem.quantity)
                        }
                    }
                }
            }
        }
    }
}

This works with simple binding. Nice... But I'd like to implement a custom binding here, so that user cannot decrement quantity below zero.

That's where getting issues.

I tried replacing my stepper binding this way :

    Stepper("", value: bindingForItemQuantity($oi))


    private func bindingForItemQuantity(_ bindedOrderItem: Binding<OrderItem>) -> Binding<Int> {
        let oi = bindedOrderItem.wrappedValue
        return Binding<Int>(
            get: { orderItem.quantity },
            set: { newValue in
                if newValue >= 1 {
                    orderItem.quantity = newValue
                }
            }
        )
    }

But i'm getting errors Cannot assign to property: 'orderItem' is a 'let' constant

I guess because my OrderItem is a struct, so i'll update it to be a class. But then my view is not automaticaly updating when using the steppers. I've been hacking it so it ended working (toggling a State bool in my Binding.set) but I guess i'm just going the wrong way...

What would be a clean binding solution here ?

Answer

Update your code with

private func bindingForItemQuantity(_ bindedOrderItem: Binding<OrderItem>) -> Binding<Int> {
        return Binding<Int>(
            get: { bindedOrderItem.quantity.wrappedValue },
            set: { newValue in
                print(newValue)
                if newValue >= 1 {
                    bindedOrderItem.quantity.wrappedValue = newValue
                }
            }
        )
    }

Enjoyed this question?

Check out more content on our blog or follow us on social media.

Browse more questions