Introduction

All software applications, or “apps” for short:

  1. accept input
  2. process that input somehow according to a set of rules or “business logic”
  3. show output

Input to an app we write can come from:

  • users interacting with controls we provide (slider, stepper, text field)
  • a device (such as a microphone or camera)
  • an online data stream (such as a remote web server or database)
  • a file

As apps grow in size and complexity, software developers use design patterns to keep a project organized and easy to understand.

Employing a design pattern in this way is known as separating concerns.

Simply put – we try to avoid placing “all our eggs in one basket” – this means – we try to avoid writing all of our app’s code within one file in a project.

The MVVM design pattern

MVVM is a software design pattern and the acronym stands for Model-View-ViewModel.

Here is a visual summary of this design pattern:

flowchart LR

id1["<b>Model</b><br/>Describes data"] --> id2["<b>View Model</b><br/>Manages the state of data<br/>Contains business logic"]
id2 --> id3["<b>View(s)</b><br/>Presents data"]
id3 --> id2

View

A view is anything the user sees within our apps or interacts with.

For example, consider this simple view – it is a structure designed to show a single item within a list – displaying a title and a subtitle:

You have already written many views while learning about layout and designing interfaces with SwiftUI. đŸ€©

Model

The model within an app describes the data our app will use – and keeps logic that is directly related to that data.

What does that mean?

Let’s look at an example – here is a model for a circle:

struct Circle {
    
    // MARK: Stored properties
    var radius: Double
    
    // MARK: Computed properties
    var diameter: Double {
        return 2 * radius
    }
    
    var perimeter: Double {
        return 2 * Double.pi * radius
    }
    
    var area: Double {
        return Double.pi * radius * radius
    }
}

When we write a structure to serve as part of the model for our app, we consider:

  1. What data it needs to store – these become stored properties.
  2. What data it should offer – what useful information it could provide – these become computed properties .

You have already created many models when authoring Swift structures to describe shapes, hockey cards, book listings
 🚀

View Model

The view model is a concept that is new to you.

We will explore the role of a view model in this lesson.

The view model (as implied by its name) sits between the model code and the view code in an app.

A view model’s job is to store the current state of data within an app, and to encapsulate (contain) any business logic (processing rules) required to make the app perform its stated function.

Think of the view model as the “brain” of your app — it handles everything that isn’t directly about drawing the user interface. Here’s what a view model might do:

Managing data

  • Keep track of the current state of information in your app
  • Load data when the app starts up
  • Save data so it’s still there the next time the app is opened

Keeping data accurate

  • Check that user input makes sense before doing anything with it (for example, making sure an email address contains an ”@” symbol)
  • Take raw data and organize or transform it into a form that’s useful for the view to display (for example, sorting a list alphabetically, or filtering out items that don’t match a search)

Talking to the outside world

  • Fetch data from a remote database or web service
  • Send new or updated data back to a remote database
  • Receive information in JSON format from an API and convert it into structures your app can work with

Keeping the view informed

  • Track whether data is currently loading, so the view can show a spinner
  • Hold onto any error messages so the view can display them to the user

The key idea is this: your view should just display information and respond to user actions — it shouldn’t have to think.

The view model does the thinking, and the view reacts to whatever the view model tells it.

NOTE

In larger, professional apps, some of these responsibilities might be split into separate layers — for example, a dedicated networking helper or a storage manager.

However, putting these responsibilities all in the view model is a reasonable approach for a beginning programmer like yourself.

Apply within a context

Let’s now apply this concept and re-write an app that you made earlier in the Revisiting Interactive Apps exercise.

In that exercise, you wrote the following app:

Of course, we were just learning, but
 the app’s interface leaves a lot to be desired.

If a user wanted to find the square of even a slightly larger number (say, 42) then they must repeatedly tap the stepper control.

This is very tedious.

As well, all of the logic for that app was kept within the view:

  1. Input was collected through the Stepper control and saved in stored property
  2. Output is calculated (via a computed property)
  3. The result is shown (via a Text control within the body property)


 like this:

struct ContentView: View {
    
    // MARK: Stored properties
    @State var base: Int = 1
    
    // MARK: Computed properties
    var squared: Int {
        return base * base
    }
    
    var body: some View {
        VStack {
            
            Spacer()
            
            HStack(alignment: .top) {
 
                Text("\(base)")
                    .font(.system(size: 96))
 
                Text("2")
                    .font(.system(size: 44))
 
                Text("=")
                    .font(.system(size: 96))
 
                Text("\(squared)")
                    .font(.system(size: 96))
            }
            
            Stepper(value: $base, label: {
                Text("Base")
            })
                        
            Spacer()
        }
        .padding()
    }
}
 
#Preview {
    ContentView()
}

Although the total lines of code are not that high, it is still considered bad practice to keep all of the logic for an app within a single file.

What the app were re-written to work as follows instead?

In this version of the app, the user can type whatever they want into the text fields.

An answer, or an appropriate error message, is shown to the user.

The user can save calculations made to refer to later on.

If we apply the MVVM design pattern, here are the specific roles each layer will play in the re-designed app:

LayerRole
ModelHold the base and exponent in numeric form; provide the result of evaluating the power.
View ModelHold the text provided by the user for the base and exponent; provide an instance of the model – the power – where possible, otherwise, provide an appropriate error message. As well, hold an array that contains a list of previous calculations performed.
ViewCollect input from the user and display whatever output is provided by the view model.

Get started

Obtain the code

If you have installed the new Xcode project templates, please now use the Advanced MVVM template to create a project named SeparationOfConcerns, like this:


 and:


 resulting in:

Try out the app

Take a moment to thoroughly try out the app. What do you notice about how it works?

Consider the code

In the exercises further below, you’ll make changes to the provided starter code to improve the app experience for users.

Before doing so, take some time here to really consider the code in this complete example of applying the MVVM design pattern.

By trying to answer the prompts below (and then checking your work by expanding the answer to each question) you will deepen your understanding of how to organize a complete app using MVVM.

TIP

It is recommended that you try responding to these questions with a peer in class.

The Model (Power.swift)

  1. Find the two stored properties in the Power structure. What data types are they? Why might those data types have been chosen?
  1. Look at the result computed property. In your own words, describe what the loop is doing. Could this have been written as a stored property instead? Why or why not?
  1. Why does the model have no idea that a user interface exists? What would be wrong with putting a TextField inside the model?

The View Model (PowersListViewModel.swift)

  1. Find the line var power: Power?. Why is this a computed property rather than a stored property? What does the ? tell you?
  1. Trace what happens when a user types "abc" into the base field. Which line of code catches that problem, and what happens as a result?
  1. The powersList property is marked private(set). What do you think that means, and why might that be a good idea?
  1. The save() function uses insert(_:at:) rather than append(). What difference does that make in the list, and why might that be preferable?
  1. Notice that the view model validates input and holds the list of saved powers. Based on what you’ve read about the role of a view model, does it make sense for both of those responsibilities to live here?

The View (PowersListView.swift and PowerItemView.swift)

  1. Find the if let power = viewModel.power block. What are the two possible outcomes, and what does the view show in each case?
  1. The Save button calls viewModel.save(power). Notice the view doesn’t directly modify powersList itself — it asks the view model to do it. Why is that distinction important?
  1. PowerItemView takes a Power directly as a parameter. Does PowerItemView know anything about the view model? Should it? Why or why not?
  1. Look at the scaleFactor parameter in PowerItemView. Why might it be useful to have the same subview handle both the “current result” display (larger) and the history list (smaller)?

Connecting the Layers

  1. When you type a new value into the Base field, several things update automatically — without any button press. Trace the chain of events: what changes, what reacts, and why?
  1. If you wanted to add a Clear History button, which layer would the new function belong in, and which layer would contain the button itself?
  1. If Power were a class instead of a struct, what might behave differently? HINT: think about what happens when you save() a power and then keep typing.

Exercises

Now, try extending the app in the following ways.

Handle zero exponents

When a zero exponent is provided, the app currently returns an error message.

Make changes first to the model, then to the view model, to support the evaluation of powers with an exponent of 0.

HINT

From math class, recall that any power with an exponent of 0 has a result of 1.

You may need to make use of selection statements, such as if or switch.

TIP

Try modifying the model, and then the view model, first within the playground you set up earlier. Once you have the code there working correctly, move it into your project.

Handle a negative base

When a negative base is provided, the app currently returns an error message.

Make changes first to the view model, then to the view, to support the evaluation of powers with a negative base.

Ensure that the view properly represents the base when it is negative.

For example, it is correct to show this:

It is not correct to show this:


 since the base in the power is and not .

NOTE

When the base is positive, there should be no brackets around it.

For example:

Do not present the result like this:

Handle negative exponents

When a negative exponent is provided, the app currently returns an error message.

Make changes first to the model, then to the view model, and finally to the view, to support the evaluation of powers with negative exponents.

TIP

When the user provides a negative exponent, express the result as a fraction.

For example,