7

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())
    }
}
Nikolai
  • 659
  • 1
  • 5
  • 10
  • 1
    You want to display the `name` property of an `ObservableObject` in a view. There is absolutely no reason to introduce a “ViewModel” here. Can you change your example to something more realistic? – rob mayoff Mar 10 '20 at 05:23
  • 1
    I'm aware that the given example is not necessarily realistic. We could directly connect the view and the model. But if we want to use the [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) pattern, for whatever reason, there must be a model of the view (called **ViewModel**) to expose properties and handle the view's logic. – Nikolai Mar 10 '20 at 06:02
  • 2
    But you haven't motivated the use of the pattern. If you try, you might discover that rarely if ever needed in Swift/SwiftUI. – rob mayoff Mar 10 '20 at 06:03
  • Remember that [you should always have a single source of truth](https://developer.apple.com/videos/play/wwdc2019/226/?time=204). In SwiftUI, you can often use extensions on your model type to add those properties and methods that might require a ViewModel in other systems. – rob mayoff Mar 10 '20 at 06:07
  • Ok, got your point! So you say, for instance, if I have a model with unfiltered data and I want to show only a portion of it (e.g. articles not older than a week), one would rather extend the model than writing a ViewModel which acts as a filter-layer? – Nikolai Mar 10 '20 at 06:14
  • Yes, that's what I would start with. – rob mayoff Mar 10 '20 at 06:49
  • Your working example was incredibly useful in my case where I am trying to implement new functionality with SwiftUI in an application that uses UIKit. – MrAn3 May 21 '20 at 11:39

0 Answers0