# Using enums with states in ViewModels

Table of Contents

In this article I want to share my experience with using enums with associated values in ViewModels. It’s a simple pattern that keeps state management clean and predictable.

States and ViewModelsLink to heading

Usually most of us are holding some kinds of states in our ViewModels. For example, we have a TodoListViewModel that holds the current state of the todo list. Possible states could be: loading, show, error, empty.

enum TodoListState {
case loading
case show
case error
case empty
}
class TodoListViewModel: ObservableObject {
@Published var state: TodoListState = .loading
@Published var todos: [Todo] = []
@Published var error: Error?
}

In a SwiftUI view we can use a switch statement to handle the different states:

struct TodoListView: View {
@ObservedObject var viewModel: TodoListViewModel
var body: some View {
switch viewModel.state {
case .loading:
ProgressView()
case .show:
List(viewModel.todos) { todo in
Text(todo.title)
}
case .error:
Text(viewModel.error?.localizedDescription ?? "Unknown error")
case .empty:
Text("No todos")
}
}
}

The state should always be the entry point in a view. Following this pattern makes it easy to navigate the code and add new states in the future.

Enum Associated ValuesLink to heading

To go one step further, we can use enums with associated values to hold the current state and the data in a ViewModel. Let’s take a look at the following enum example:

enum TodoListState {
case loading
case show([Todo])
case error(Error)
case empty
}

With this approach we can hold the current state and the data in one enum case.

States and Enums with Associated ValuesLink to heading

With these two approaches, we can remove the todos: [Todo] and the error: Error? properties in the ViewModel. The TodoListViewModel now looks like this:

class TodoListViewModel: ObservableObject {
@Published var state: TodoListState = .loading
}

Our TodoListView now looks like this:

struct TodoListView: View {
@ObservedObject var viewModel: TodoListViewModel
var body: some View {
switch viewModel.state {
case .loading:
ProgressView()
case .show(let todos):
List(todos) { todo in
Text(todo.title)
}
case .error(let error):
Text(error.localizedDescription)
case .empty:
Text("No todos")
}
}
}

Now we have a much cleaner ViewModel with just one state property that encapsulates all the data we need. The view logic becomes more straightforward since we’re extracting the data directly from the enum cases.

ConclusionLink to heading

Using enums with associated values in a ViewModel isn’t a big deal, but it’s a great way to hold the current state and data in one enum case.

My experience is that it’s better to keep ViewModels as small as possible. Over time, ViewModels can grow and it becomes hard to keep them clean. Enums with associated values are a great way to achieve this. It’s also easier to add new states in the future and teammates can easily find their way in the code.

My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts