MVVM in SwiftUI

MVVM in SwiftUI

What is MVVM?

MVVM stands for Model-View-ViewModel. It is a design pattern used in software development to separate the user interface (View) from the business logic and data (Model) through an intermediary (ViewModel). This separation helps in managing complex applications, improving testability, and enhancing maintainability.

Model

The Model represents the data and business logic of the application. It is responsible for managing the data, whether it comes from a database, a web service, or any other source.

TodoItgem.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Foundation

struct TodoItem : Identifiable, Codable {
let id: UUID
var title: String
var isCompleted: Bool

init(id: UUID = UUID(), title: String, isCompleted: Bool = false) {
self.id = id
self.title = title
self.isCompleted = isCompleted
}
}

ViewModel

The ViewModel acts as a bridge between the Model and the View. It retrieves data from the Model, processes it if necessary, and exposes it in a way that the View can easily consume.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Foundation
import SwiftUI
import Combine

extension ContentView {
@MainActor class ViewModel: ObservableObject {
@Published var itemList: [TodoItem] = []

func addItem() {
let todo = TodoItem(id: UUID(), title: "Do something!")
withAnimation {
itemList.insert( todo, at: 0)
}
}
}
}

Here we define a ViewModel class that conforms to ObservableObject. It has a published property itemList that holds an array of TodoItem. The addItem method adds a new item to the list.

@Published is a property wrapper that allows SwiftUI views to automatically update when the property changes.

ContentView has addItem method to add new items to the list.

View

The View is responsible for displaying the user interface. It observes the ViewModel and updates the UI when the data changes.

Content-ContentView.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import SwiftUI

struct ContentView: View {
@StateObject private var vm = ViewModel()

var body: some View {
VStack {
List(vm.itemList, id: \.id) { item in
HStack {
Text(item.title)
}
}
.listStyle(.plain)
.background( .thinMaterial)

Button("Add Item") {
vm.addItem()
}
}
}
}

#Preview {
ContentView()
}

In the view, we create a ViewModel with @StateObject. ViewModel is observed by the view, and when the itemList changes, the List will automatically update to reflect the new data. The Button calls the addItem method in the ViewModel to add a new item to the list.

@StateObject vs @ObservedObject

@StateObject is used to create a single instance of a ViewModel that is owned by the View. It is initialized only once during the lifecycle of the View.

@ObservedObject is used to observe an existing instance of a ViewModel that is owned by another View. It does not create a new instance but rather listens for changes in the existing one.

Difference:

  • Use @StateObject when the View is responsible for creating and owning the ViewModel
  • Use @ObservedObject when the View is receiving the ViewModel from another source, such as a parent View.
  • In this example, we use @StateObject because the ContentView is responsible for creating and owning the ViewModel.