# 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.