Cannot convert Binding<Type> to Binding<Protocol>
is the specific error message, but the underlying question is: What's a good architecture for holding a reference, then mutating & reading from the underlying object?
Examples:
- In an RPG, your character has a currentArmor. You want it to take damage.
- In Pokemon, you track a currentPokemon. It needs to level up.
- In Instagram, you have a selectedPost, you want the like button to mutate the underlying post.
Here's a concrete example showing what I tried: This app has several Animal
s. You can use the app to edit the currentAnimal
's name
.
The error Cannot assign value of type 'Binding<Dog>' to type 'Binding<Animal>'
:
protocol Animal {
var name: String { get set }
}
struct Cow: Animal {
var name: String
}
struct Dog: Animal {
var name: String
}
class Model: ObservableObject {
@Published var dog: Dog = Dog(name: "dog 1")
// ** THIS IS THE IMPORTANT PART ** //
@Published var currentAnimal: Binding<Animal> = .constant(Cow(name: "cow 1"))
}
struct ContentView: View {
@StateObject var model = Model()
var body: some View {
VStack {
Text(model.dog.name) // Show the underlying Dog
}
.onAppear {
model.currentAnimal = $model.dog // Cannot assign value of type 'Binding<Dog>' to type 'Binding<Animal>'
}
.onTapGesture {
model.currentAnimal.wrappedValue.name = "dog 2" // Mutate state here
}
}
}
Yes, I'm aware of https://developer.apple.com/forums/thread/652064 But I don't think doing a force as!
case is a good idea. Is there some way that generics instead of protocols could solve this problem? Or class inheritance instead of protocols? Or some other architecture?
CodePudding user response:
EDIT: With Classes it works:
protocol Animal {
var name: String { get set }
}
class Cow: Animal {
var name: String
init(name: String) {
self.name = name
}
}
class Dog: Animal {
var name: String
init(name: String) {
self.name = name
}
}
class Model: ObservableObject {
@Published var dog: Dog = Dog(name: "dog 1")
// ** THIS IS THE IMPORTANT PART ** //
@Published var currentAnimal: Animal = Cow(name: "cow 1")
}
struct ContentView: View {
@StateObject var model = Model()
var body: some View {
VStack {
Text(model.dog.name) // Show the underlying Dog
}
.onAppear {
model.currentAnimal = model.dog // Cannot assign value of type 'Binding<Dog>' to type 'Binding<Animal>'
}
.onTapGesture {
model.currentAnimal.name = "dog 2" // Mutate state here
}
}
}