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