How to connect published properties of model and viewmodel in Swift?

1.5k Views Asked by At

Let's assume a model, which implements the protocol ObservableObject and has got a @Published property name.

// MARK: Model
class ContentSinglePropertyModel: ObservableObject {
    @Published public var name: String
}

Now, I would like to display that name in a view and update the view, whenever name in the model changes. Additionally, I would like to use the Model-View-ViewModel (MVVM) pattern to achieve this goal.

// MARK: ViewModel
final class ContentSinglePropertyViewModel: ObservableObject {
    private let model: ContentSinglePropertyModel

    @Published var name: String = ""

    init() {
        self.model = ContentSinglePropertyModel()
    }
}

// MARK: View
struct ContentSinglePropertyView: View {
    @ObservedObject var viewModel: ContentSinglePropertyViewModel

    var body: some View {
        Text(self.viewModel.name)
    }
}

Since I don't like the idea to make the model or it's properties public within the viewmodel, one option is to wrap the model's property name in the viewmodel. My question is: How to connect the name of the model and the viewmodel in the most idiomatic way?

I've came up with the solution to update the viewmodel's property through the use of Combine's assign method:

self.model.$name.assign(to: \.name, on: self).store(in: &self.cancellables)

Is there a better solution?

My working example:

import SwiftUI
import Combine

// MARK: Model
class ContentSinglePropertyModel: ObservableObject {
    @Published public var name: String

    init() {
        self.name = "Initial value"
    }

    func doSomething() {
        self.name = "Changed value"
    }
}

// MARK: ViewModel
final class ContentSinglePropertyViewModel: ObservableObject {
    private let model: ContentSinglePropertyModel
    private var cancellables: Set<AnyCancellable> = []

    @Published var name: String = ""

    init() {
        self.model = ContentSinglePropertyModel()

        // glue Model and ViewModel
        self.model.$name.assign(to: \.name, on: self).store(in: &self.cancellables)
    }

    func doSomething() {
        self.model.doSomething()
    }
}

// MARK: View
struct ContentSinglePropertyView: View {
    @ObservedObject var viewModel: ContentSinglePropertyViewModel

    var body: some View {
        VStack {
            Text(self.viewModel.name)

            Button("Do something!", action: {
                self.viewModel.doSomething()
            })
        }
    }
}

struct ContentSinglePropertyView_Previews: PreviewProvider {
    static var previews: some View {
        ContentSinglePropertyView(viewModel: .init())
    }
}
0

There are 0 best solutions below