Search code examples
iosswiftswiftuixcode11combine

Can I create a generic ObservableObject class which can be used by multiple ContentViews?


Hi I was just wondering is it possible to create a generic class confirming to ObservableObject protocol which can be used by more then one ContentViews.

If i can do that then I will be able make my ContentView and Model class completely generic and reusable.

An example of what i would like to achieve:

protocol ContentViewModelType: ObservableObject {
    var propertyToInitialiseView: [String] { get }
}

struct ContentView: View {
    @ObservedObject var viewModel: some ViewModel

    var body: some View {
        Text("Hello World")
    }
}

If I can do that any class can implement ContentViewModelType and become a model for ContentView which makes it generic and reusable. For example

class ViewModel: ObservableObject {
    var objectWillChange = PassthroughSubject<ViewModel, Never>()
}

But when i try to initialise ContentView that xcode gives me a type error.

enter image description here

I thought the whole point of introducing some keyword was so that we can use protocol as type for those protocols that have associated type as a requirement and hence this should work. But it gives an error.

If anyone has any references or knowledge about this problem that they could share or possibly a solution for this it would be great.

Thanks in advance.


Solution

  • Trying to understand your question, and I am not quite sure I understand the intent... but to create a view which takes in generic view model (based on the protocol you had before) you will need the following code:

    protocol ViewModelWithProperties: ObservableObject {
        var properties: [String] { get }
    }
    
    struct SomeView<T>: View where T: ViewModelWithProperties {
    // this can also be written as 
    // struct SomeView<T: ViewModelWithProperties>: View {
        @ObservedObject var item: T
    
        var body: some View {
            VStack {
                ForEach(item.properties, id: \.self) {
                    Text($0)
                }
            }
        }
    }
    

    To consume this instance you will need to:

    struct ContentView: View {
        var body: some View {
            SomeView(item: MyViewModel())
        }
    }
    

    As stated in one of the other answers, some is used for opaque types, it doesn't make your code generic.