Home > Back-end >  Cannot convert Binding<Type> to Binding<Protocol>
Cannot convert Binding<Type> to Binding<Protocol>

Time:02-13

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 Animals. 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
        }
    }
}
  • Related