Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define a protocol to include a property with @Published property wrapper

When using @Published property wrapper following current SwiftUI syntax, it seems very hard to define a protocol that includes a property with @Published, or I definitely need help :)

As I'm implementing dependency injection between a View and it's ViewModel, I need to define a ViewModelProtocol so to inject mock data to preview easily.

This is what I first tried,

protocol PersonViewModelProtocol {
    @Published var person: Person
}

I get "Property 'person' declared inside a protocol cannot have a wrapper".

Then I tried this,

protocol PersonViewModelProtocol {
    var $person: Published
}

Obviously didn't work because '$' is reserved.

I'm hoping a way to put a protocol between View and it's ViewModel and also leveraging the elegant @Published syntax. Thanks a lot.

like image 293
UndergroundFox Avatar asked Aug 15 '19 23:08

UndergroundFox


2 Answers

You have to be explicit and describe all synthetized properties:

protocol WelcomeViewModel {
    var person: Person { get }
    var personPublished: Published<Person> { get }
    var personPublisher: Published<Person>.Publisher { get }
}

class ViewModel: ObservableObject {
    @Published var person: Person = Person()
    var personPublished: Published<Person> { _person }
    var personPublisher: Published<Person>.Publisher { $person }
}
like image 110
Mycroft Canner Avatar answered Sep 21 '22 08:09

Mycroft Canner


A workaround my coworker came up with is to use a base class that declares the property wrappers, then inherit it in the protocol. It still requires inheriting it in your class that conforms to the protocol as well, but looks clean and works nicely.

class MyPublishedProperties {
    @Published var publishedProperty = "Hello"
}

protocol MyProtocol: MyPublishedProperties {
    func changePublishedPropertyValue(newValue: String)
}

class MyClass: MyPublishedProperties, MyProtocol {
    changePublishedPropertyValue(newValue: String) {
        publishedProperty = newValue
    }
}

Then in implementation:

class MyViewModel {
    let myClass = MyClass()

    myClass.$publishedProperty.sink { string in
        print(string)
    }

    myClass.changePublishedPropertyValue("World")
}

// prints:
//    "Hello"
//    "World"
like image 40
LazyTriceratops Avatar answered Sep 20 '22 08:09

LazyTriceratops