I have a swiftui view that depends on a view model, the view model has some published properties. I want define a protocol and default implementation for the view model hierarchy, and make the view dependent on the protocol not the concrete class?
I want to be able to write the following:
protocol ItemViewModel: ObservableObject {
@Published var title: String
func save()
func delete()
}
extension ItemViewModel {
@Published var title = "Some default Title"
func save() {
// some default behaviour
}
func delete() {
// some default behaviour
}
}
struct ItemView: View {
@ObservedObject var viewModel: ItemViewModel
var body: some View {
TextField($viewModel.title, text: "Item Title")
Button("Save") { self.viewModel.save() }
}
}
// What I have now is this:
class AbstractItemViewModel: ObservableObject {
@Published var title = "Some default Title"
func save() {
// some default behaviour
}
func delete() {
// some default behaviour
}
}
class TestItemViewModel: AbstractItemViewModel {
func delete() {
// some custom behaviour
}
}
struct ItemView: View {
@ObservedObject var viewModel: AbstractItemViewModel
var body: some View {
TextField($viewModel.title, text: "Item Title")
Button("Save") { self.viewModel.save() }
}
}
Wrappers and stored properties are not allowed in swift protocols and extensions, at least for now. So I would go with the following approach mixing protocols, generics and classes... (all compilable and tested with Xcode 11.2 / iOS 13.2)
// base model protocol
protocol ItemViewModel: ObservableObject {
var title: String { get set }
func save()
func delete()
}
// generic view based on protocol
struct ItemView<Model>: View where Model: ItemViewModel {
@ObservedObject var viewModel: Model
var body: some View {
VStack {
TextField("Item Title", text: $viewModel.title)
Button("Save") { self.viewModel.save() }
}
}
}
// extension with default implementations
extension ItemViewModel {
var title: String {
get { "Some default Title" }
set { }
}
func save() {
// some default behaviour
}
func delete() {
// some default behaviour
}
}
// concrete implementor
class SomeItemModel: ItemViewModel {
@Published var title: String
init(_ title: String) {
self.title = title
}
}
// testing view
struct TestItemView: View {
var body: some View {
ItemView(viewModel: SomeItemModel("test"))
}
}