1

I have

class ViewModel: ObservableObject {
    @Published var vehicle: any Vehicle
}

then in the view

var body : some View {
   if let truck = vm.vehicle as? Truck {
           TextField("Name", text: $vm.truck.name)    // how to write this so it compiles?
   }
}

name is a property of Truck, but not Vehicle. What I'm trying to do, is if it's castable to a truck then show this textfield, otherwise don't show it.

erotsppa
  • 14,248
  • 33
  • 123
  • 181

2 Answers2

2

You can add a new Truck? property in vm, that returns non-nil if vehicle is Truck:

var truck: Truck? {
    get { vehicle as? Truck }
    set {
        if let value = newValue {
            vehicle = value
        }
    }
}

Then, use the Binding initialiser that turns a Binding<T?> into a Binding<T>? (see also my answer here)

if let binding = Binding($vm.truck) {
    TextField("Foo", text: binding.name)
}

Alternatively, make your own Binding with the init(get:set:) initialiser.

extension Binding {
    init?<V>(_ binding: Binding<V>) {
        let optionalBinding = Binding<Value?>(get: { binding.wrappedValue as? Value }, set: {
            if let v = $0 as? V {
                binding.wrappedValue = v
            }
        })
        if let new = Binding(optionalBinding) {
            self = new
        } else {
            return nil
        }
    }
}
if let binding = Binding<Truck>($vm.vehicle) {
    TextField("Foo", text: binding.name)
} 
Sweeper
  • 213,210
  • 22
  • 193
  • 313
0

you could try this approach:

if var truck = vm.vehicle as? Truck {
    TextField("Name", text: Binding(
        get: { truck.name },
        set: { truck.name = $0 })
    )
    .onSubmit {
        vm.vehicle = truck
    }
}