Docs
Documentation Fundamentals

MVVM

πŸ“ What is MVVM?

MVVM stands for:

  • Model: The data layer β€” handles business logic and data management.

  • View: The UI layer β€” displays data to the user and handles user input.

  • ViewModel: The mediator β€” transforms model data into a format the view can use and reacts to user input.

🧠 Why Use MVVM?

  • Clean separation of concerns

  • Easier unit testing

  • Reusable and modular ViewModels

  • Simplifies complex UIs with state bindings

🧭 MVVM Diagram

flowchart LR
	View["View (SwiftUI)"]
	ViewModel["ViewModel (ObservableObject)"]
	Model["Model (Data / Network)"]

	View -- "binds to @Published" --> ViewModel
	ViewModel -- "fetches, transforms" --> Model
	ViewModel -- "provides @Published data" --> View

πŸ›  Example: MVVM with SwiftUI

1. Model

struct User: Identifiable, Decodable {
    let id: Int
    let name: String
    let email: String
}

2. ViewModel

import Foundation

@MainActor
class UserViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading: Bool = false

    func fetchUsers() async {
        isLoading = true
        defer { isLoading = false }

        do {
            let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
            let (data, _) = try await URLSession.shared.data(from: url)
            users = try JSONDecoder().decode([User].self, from: data)
        } catch {
            print("Error fetching users: \(error)")
        }
    }
}

3. View

import SwiftUI

struct UserListView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        NavigationView {
            Group {
                if viewModel.isLoading {
                    ProgressView()
                } else {
                    List(viewModel.users) { user in
                        VStack(alignment: .leading) {
                            Text(user.name).bold()
                            Text(user.email).font(.subheadline).foregroundColor(.gray)
                        }
                    }
                }
            }
            .navigationTitle("Users")
        }
        .task {
            await viewModel.fetchUsers()
        }
    }
}

πŸ” Data Flow Summary

sequenceDiagram
    participant View
    participant ViewModel
    participant Model

    View->>ViewModel: User triggers `.task`
    ViewModel->>Model: Fetch data from API
    Model-->>ViewModel: Return [User]
    ViewModel-->>View: Update @Published users
    View->>View: Re-render UI based on updated users

βœ… Benefits of This Approach

  • View remains declarative and clean

  • ViewModel handles all side effects and transformation

  • Model can evolve independently (local or network)

πŸ§ͺ Unit Testing the ViewModel

import XCTest
@testable import YourApp

final class UserViewModelTests: XCTestCase {
    func testFetchUsersPopulatesArray() async {
        let viewModel = UserViewModel()
        await viewModel.fetchUsers()
        XCTAssertFalse(viewModel.users.isEmpty)
    }
}

🧱 MVVM Best Practices

  • Keep your ViewModel testable (avoid UIKit/SwiftUI references)

  • Use @Published for observable state

  • Use async/await or Combine for asynchronous logic

  • Prefer dependency injection for services

🧭 Advanced MVVM Diagram with Services

flowchart TD
    View[View]
    ViewModel[ViewModel]
    UserService[UserService Protocol]
    UserAPI[UserAPIService]

    View --> ViewModel
    ViewModel --> UserService
    UserService --> UserAPI
    UserAPI --> Model[Model/Data]

πŸ“¦ Conclusion

MVVM brings clarity and scalability to Swift apps. By structuring your app with well-defined roles, you get a clean separation of UI, state, and business logic β€” and a better experience for both developers and users.

Docs
Copyright Β© Mitch Lang. All rights reserved.