Search code examples
swiftswiftuicombine

Generalizing multiple view models


I have written a couple of view models for my SwiftUI project. It turns out that they share quite a lot of properties and code and I wanted to pull this code into a generic view model and then user class inheritance to specialize the real view models. Unfortunately this turns out quite difficult. Here's a simplified example:

class viewModelA: ObservableObject {
    enum Animal {
        case cat
        case dog
    }

    @published var selected: Animal?

    func select(_ animal: Animal?) {
        self.selected = animal
    }
    ...
}

class viewModelB: ObservableObject {
    enum Animal {
        case lion
        case tiger
    }

    @published var selected: Animal?

    func select(_ animal: Animal?) {
        self.selected = animal
    }
    ...
}

The first thing I've tried is to create a protocol and use a protocol with an associatedtype for Animal, but then I struggled with the property that has @published. Swift doesn't allow to have property wrappers in protocols...

How could I generalize those 2 classes?


Solution

  • Here is first possible approach (protocol-based):

    protocol AnimalModel: ObservableObject {
        associatedtype Animal
        var selected: Animal? { get set }
    }
    
    class ViewModelA: AnimalModel {
        enum Animal {
            case cat
            case dog
        }
    
        @Published var selected: ViewModelA.Animal?
    }
    
    class ViewModelB: AnimalModel {
        enum Animal {
            case lion
            case tiger
        }
    
        @Published var selected: ViewModelB.Animal?
    }
    

    and here is second one (inheritance-based):

    class ViewModel<Animal>: ObservableObject {
        @Published var selected: Animal?
    }
    
    class ViewModelA: ViewModel<ViewModelA.Animal> {
        enum Animal {
            case cat
            case dog
        }
    }
    
    class ViewModelB: ViewModel<ViewModelB.Animal> {
        enum Animal {
            case lion
            case tiger
        }
    }