SwiftUI - Relationship between View and ViewModel in a MVVM architectural pattern.

Agostino Careddu
OverApp
Published in
7 min readMar 1, 2023

--

If you are a novice developer and you just bumped into Swift as your first programming language, you might be probably interfacing with the last Apple’s framework called SwiftUI.

Let’s make a quick clarification about this first, this article is going to be based on SwiftUI framework, a framework is a tool that provides components or custom solutions that are used to make the development faster and in Swift there are 2 great frameworks to build your App with, they are called UIKit and SwiftUI:

  1. we can describe UIKit as an Imperative UI, which simply means that you state “how to do something”, this framework was presented by Apple in July 2008;
  2. instead SwiftUI is a Declarative UI, this means that you basically declare “something you want to happen”, this framework was presented by Apple at the WWDC in 2019.

After this required explanation I would like to introduce you to the MVVM or Model-View-ViewModel pattern, which is an architectural pattern and it is the one that I am going to use in this project.

This kind of pattern is composed of 3 elements:

  1. The Model: theoretically it represents the data on which the app works. Basically under the model are represented all those entities (classes, structures) that contain informations obtained from an application through an API, or from a database, or generated/collected by it. Quite often the expression used to refer to this concept is Domain Model.
  2. The View: theoretically the view is the structure, layout, and appearance of what a user sees on the screen. It displays a representation of the model and receives the user’s interaction with the view (mouse taps, keyboard inputs, swipes, etc.) and it forwards the handling of these interactions to the view model via the data binding (properties, event callbacks, etc.) that is defined to link the view and the view model.
  3. The ViewModel: theoretically it stores and represents the data to be presented on a specific view and implements the functionality to manipulate it.
    In essence it is a class that contains one or more properties representing the informations that have to be shown or used by a specific view. It implements methods (also called commands) that respond to events called by the view when the user interacts with it. If the properties containing the data change, the view will immediately update itself through a binding technique to reflect this change.

What does binding mean then?

A binding is, like its name implies, a connection between two things. In SwiftUI, a binding sits between a property that stores data (which it will be stored in our ViewModel marked as Published) and a View that displays and changes based on that data.

Below there is a graphical explanation of the MVVM pattern’s flow:

MVVM Architectural Pattern flow

There is not the best pattern in developing, but the answer in choosing the pattern is to choose the one which suits better your project’s needs, which change from time to time, but the choice depends on many different factors and I personally reckon that MVVM pattern is usually preferred due to those 3 reasons:

  1. Maintenance: the code is easier to maintain, especially we keep a clear distinction between the 3 elements of the pattern in which it is based on;
  2. Reusable code: with the separation between logic and UI, it is easier for a developer to work precisely on each aspect of the project and in a certain way ensures better and more effective code reuse;
  3. Testability: Unit testing is a fundamental practice in software development. Keeping the components separated in MVVM contributes to the design of effective unit tests.

After we have solidified this basic theory I am going to explain you practically, with a sample App, how the relationship between View and ViewModel works and the way these 2 elements communicate with each other.

You can clone the entire project named “SportApp” from my gitHub repository, click here to download it.

The purpose of the app is to give the user the ability to create its own workout of the day (shortly named wod) starting from a few basic movements and for any of those exercises the user can set the weight to put on and add them to a list.

A short demo of how to add an exercise to the wod list and how to remove it.

Let’s start explaining step by step the implementation. To begin with I made a Model, which I called Exercise, I made it as an enum with 3 cases (you can add more if you like or you can customize them with the sport’s exercises you prefer) where after the cases declaration I set a computed property called “var Image” that associates for each case a related image.

import Foundation
import SwiftUI

enum Exercise: String {
case benchPress
case squat
case weightedPushUp

var image: Image {
switch self {
case .benchPress:
return Image("BenchPress")
case .squat:
return Image("Squats")
case .weightedPushUp:
return Image("Pushups")
}
}
}

In addition I made my ViewModel. I created then a class named HomeViewModel, this naming helps to tidy up and make the code more readable, in fact the HomeViewModel is the one associated to the HomeView (our main View) and I made it compliant to the ObservableObject protocol, in the way that is going to synthesizes an objectWillChange publisher that emits the changed values before any of its @Published properties changes (e.g. var isDetailSheetPresented, var backgroundColor and so on).

Further down in the code I implemented 2 functions, the first one is the one I used to add items to the wod list ( func addWodToList(item: String){…} ) and the other one to delete them from the list ( func delete(at offsets: IndexSet){…} ).

import Foundation
import SwiftUI

@MainActor
class HomeViewModel: ObservableObject {

let exercises: [Exercise]

init(
exercises: [Exercise]
) {
self.exercises = [.benchPress, .squat, .weightedPushUp]
}

@Published var isDetailSheetPresented: Bool = false
@Published var isSettingsPresented: Bool = false
@Published var backgroundColor: Color = .blue

@Published var title: String = ""
var sheetTitle: String = ""
var exercisesListTitle: String = "Tap to select exercise:"
var workOutListTitle: String = "My workout of the day:"

var wodList: [String] = []

func addWodToList(item: String) {
wodList.append(item)
}

func delete(at offsets: IndexSet) {
wodList.remove(atOffsets: offsets)
}
}

Lastly I made the View that I simply called HomeView. As you can notice what leaps out from the beginning of the struct is the declaration of the viewModel as @ObservedObject, it is exactly with this that we have created a really close relationship between the View and the viewModel (e.g. every time a published var changes its value the View updates itself and it does what we set in the logic to do in that case).

I defined the SheetView and the ExerciseRow in two separated files, I usually do this for a better organization of the code, I simply split the view into smaller views in order to reuse them in other part of the project if needed.

import SwiftUI

struct HomeView: View {
@ObservedObject var viewModel: HomeViewModel

var body: some View {
VStack(spacing: 0) {

exercisesList

VStack {
Text(viewModel.workOutListTitle)
.font(.headline)
.padding()

List {
ForEach(viewModel.wodList, id: \.self) { item in
WodListRow(
exerciseType: item
)
}
.onDelete(perform: delete)
}
}
.background(viewModel.backgroundColor)
.opacity(0.8)
.scrollContentBackground(.hidden)

Button {
viewModel.isSettingsPresented = true
} label: {
Image(systemName: "gearshape.2")
.imageScale(.large)
.foregroundColor(Color.black)
}
}
.ignoresSafeArea()
.padding()
}

func delete(at offsets: IndexSet) {
viewModel.delete(at: offsets)
}

private var exercisesList: some View {
List{
Text(viewModel.exercisesListTitle)
.font(.headline)
.padding()

ForEach(viewModel.exercises, id: \.self) { exercise in
ExerciseRow(
exercise: exercise,
iconColor: .yellow,
onTapAction: {
viewModel.isDetailSheetPresented.toggle()
viewModel.title = exercise.rawValue
}
)
}
}
.background(viewModel.backgroundColor)
.opacity(0.8)
.scrollContentBackground(.hidden)
}
}

There is also a button at the bottom of the HomeView that has role to custom the background color using a modifier.

I used 2 different view modifiers for presentation in this app:

  • one of them is the “.sheet” modifier which I used to present a view that partly cover the underlying screen to set the weight of the exercises and add them to the wod list via the button Add.
.sheet(isPresented: $viewModel.isDetailSheetPresented) {
SheetView(
showModal: $viewModel.isDetailSheetPresented,
title: viewModel.title,
exerciseProgram: "Choose the weight to put on"
) { exercise in
viewModel.addWodToList(item: exercise)
}
}
  • the other one is the “.actionSheet” which provides a modal alert that appears towards the bottom of the screen and shows three buttons with the color options that when tapped are going to update the variable backgroundColor in the ViewModel and consequently set the new View background color.
.actionSheet(isPresented: $viewModel.isSettingsPresented) {
ActionSheet(title: Text("Select a background"),
buttons: [
.default(Text("Red")) {
viewModel.backgroundColor = .red
},
.default(Text("Green")) {
viewModel.backgroundColor = .green
},
.default(Text("Blue")) {
viewModel.backgroundColor = .blue
},
])
}
The actionSheet modifier in use

There are a lot of built-in view modifiers in SwiftUI that you can apply to the code and they allow you to easily modify the existing view. You can even create a custom ones based on your necessity.

I thank you greatly for reading this article and I hope it will have helped those developers who were trying to figure out what there was behind the relationship between the View and the ViewModel using SwiftUI with a MVVM pattern.

If you found this article helpful please give a clap 👏🏼.

In conclusion I want to thank my colleague Davide for giving me suggestions on writing the article.

--

--

iOS Developer | Test Automation Enthusiast | React Native Developer