Chapters

Hide chapters

SwiftUI Cookbook

Live Edition · iOS 16.4 · Swift 5.8.1 · Xcode 14.3.1

Separating View Logic with View Models
Written by Team Kodeco

SwiftUI provides a declarative way of building user interfaces. However, as your application grows, the complexity of your SwiftUI views can quickly increase. One effective strategy to reduce complexity and separate concerns is to make use of view models.

View models are an integral part of the Model-View-ViewModel (MVVM) architectural pattern. They abstract the data and behavior related to a view into a separate component. This abstraction facilitates code readability, decouples your views from your data and enables easier testing by isolating the business logic.

View models act as a bridge between views and the model layer of your app. By offloading the data manipulation and business logic from the view to the view model, the view solely focuses on presenting the data and user interface.

Let’s illustrate this with an example.

Before Using a View Model

struct ContentView: View {
  @State private var title = "Hello, World!"

  var body: some View {
    VStack {
      Text(title)
      Button(action: {
        title = "Button was tapped."
      }) {
        Text("Tap me")
      }
    }
  }
}

Your preview should look as follows for the original as well as the refactored code:

Move business logic to a view model.
Move business logic to a view model.

In this example, the ContentView itself is responsible for managing its own data state, with a title property, and it directly manipulates this property when the button is tapped.

While this may work for simple scenarios, it’s a pattern that doesn’t scale well. As more interactions and data are added to the view, the view will become bloated with state management and business logic. This leads to several issues:

  1. Testing becomes challenging. With business logic embedded in the view, unit testing the logic requires instantiating the full view, making tests more complex and less reliable.
  2. Code readability suffers. The view’s code becomes cluttered with logic that isn’t directly related to its primary purpose: defining the user interface. This makes the code harder to read and understand.
  3. Reuse is hindered. If another view needs to perform the same or similar action, there’s no easy way to share this logic, leading to code duplication.

By moving to a view model structure, we can address these problems and structure our code for scalability and maintainability. Let’s see how we can refactor this example:

After Using a View Model

Now, let’s refactor the code to use a view model.

class ContentViewModel: ObservableObject {
  @Published var title = "Hello, World!"

  func buttonTapped() {
    title = "Button was tapped."
  }
}

struct ContentView: View {
  @StateObject var viewModel = ContentViewModel()

  var body: some View {
    VStack {
      Text(viewModel.title)
      Button(action: viewModel.buttonTapped) {
        Text("Tap me")
      }
    }
  }
}

In the example above, we’ve moved the logic of updating the title when the button is tapped into a separate ContentViewModel. The ContentView now observes the title property of the ContentViewModel and calls its buttonTapped method when the button is pressed.

This separation of concerns keeps the ContentView clean and focused on its role of displaying the user interface. It also makes the business logic in ContentViewModel easier to test independently of the UI and promotes code reusability.

In conclusion, using view models is a powerful pattern that can make your SwiftUI code more maintainable and easier to test. By abstracting the data and behavior related to a view into a separate component, your code becomes more focused and easier to reason about.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.