Model-view-ViewModel (MVVM) is a powerful architectural pattern designed to separate the UI logic from the business logic. This makes applications easier to test, maintain, and extend. Due to its declarative nature, MVVM is a preferred choice for developers working with SwiftUI. In this blog post, we’ll delve into MVVM’s principles and demonstrate its application through a sample employee management app.
What is MVVM?
At its core, MVVM is structured into three components:
1. Model
● Represents the data layer.
● Handles the business logic and communicates with data sources such as databases or APIs.
2. View
● Represents the UI layer.
● Displays the data provided by the ViewModel and reacts to user interactions.
3. ViewModel
● Transforms data from the Model into a format suitable for the View.
● Notifies the View when data changes, often through bindings or observers.
Implementing MVVM in the Employee Management App
Let’s break down the provided code to see how it adheres to the MVVM pattern.
Model: EmployeeDetailModel
The EmployeeDetailModel serves as the data structure representing an employee. It encapsulates properties such as the employee’s name, designation, experience, and salary.
struct EmployeeDetailModel: Identifiable {
var id: UUID = UUID()
var employeeName: String?
var employeeDesignation: String?
var employeeExperience: String?
var employeeSalary: String?
var offset: CGFloat? = 0
var isSwiped: Bool? = false
}
ViewModel: EmployeeListViewModel
The EmployeeListViewModel is responsible for managing the list of employees and handling business logic like adding or deleting employees. It exposes data and methods for the View to consume.
Example Responsibilities:
● employeeList: An array of EmployeeDetailModel instances.
● moveToAddEmployee(): Handles navigation to the Add Employee screen.
● deleteEmployee(): Removes an employee from the list.
class EmployeeListViewModel: ObservableObject {
@Published var employeeList: [EmployeeDetailModel] = []
@Published var isMoveToAddEmployee: Bool = false
func moveToAddEmployee() {
isMoveToAddEmployee = true
}
func deleteEmployee(employeeDetail: EmployeeDetailModel) {
employeeList.removeAll { $0.id == employeeDetail.id }
}
func addEmployee(employeeInfo: EmployeeDetailModel) {
employeeList.append(employeeInfo)
}
}
ViewModel: AddEmployeeViewModel
The AddEmployeeViewModel is responsible for managing the data input for creating a new employee. It ensures that the AddEmployeeView operates independently of the business logic.
Example Responsibilities:
● Holds input data for a new employee.
● Creates a new EmployeeDetailModel instance when the user submits the form.
class AddEmployeeViewModel: ObservableObject {
@Published var empName = ""
@Published var empDesignation = ""
@Published var empExperience = ""
@Published var empSalary = ""
init() {}
func addEmployee() -> EmployeeDetailModel {
let employeeInfo = EmployeeDetailModel(employeeName: empName, employeeDesignation: empDesignation, employeeExperience: empExperience, employeeSalary: empSalary)
return employeeInfo
}
}
View: EmployeeListView
The EmployeeListView is a SwiftUI View responsible for presenting the employee list and responding to user actions. It communicates with the ViewModel to fetch and update data.
Key Features:
● Uses @ObservedObject to bind to the EmployeeListViewModel.
● Renders a list of employees using ForEach.
● Provides a UI to add and delete employees.
struct EmployeeListView: View {
@ObservedObject var viewModel = EmployeeListViewModel()
var body: some View {
NavigationStack {
ZStack {
VStack(spacing: 0) {
HStack {
Spacer()
Image(systemName: "plus")
.onTapGesture {
viewModel.moveToAddEmployee()
}
}
.padding()
ScrollView {
LazyVStack(spacing: 10) {
ForEach(viewModel.employeeList) { employee in
EmployeeListItemView(employeeDetail: employee) {
viewModel.deleteEmployee(employeeDetail: employee)
}
}
}
}
}
if viewModel.employeeList.isEmpty {
Text("No Data Found")
.foregroundColor(.gray)
}
}
.navigationDestination(isPresented: $viewModel.isMoveToAddEmployee) {
AddEmployeeView(employeeInfoDelegate: viewModel)
}
}
}
}
View: AddEmployeeView
This view allows users to add a new employee. It communicates back to the EmployeeListViewModel through the EmployeeInfoDelegate protocol.
struct AddEmployeeView: View {
@ObservedObject var viewModel = AddEmployeeViewModel()
var employeeInfoDelegate: EmployeeInfoDelegate?
var body: some View {
VStack {
TextField("Employee Name", text: $viewModel.empName)
TextField("Employee Designation", text: $viewModel.empDesignation)
TextField("Experience", text: $viewModel.empExperience)
TextField("Salary", text: $viewModel.empSalary)
Button("Add Employee") {
let newEmployee = viewModel.addEmployee()
employeeInfoDelegate?.addEmployee(employeeInfo: newEmployee)
}
}
.padding()
}
}
Why Use MVVM?
Separation of Concerns: MVVM ensures that the UI logic and business logic are decoupled.
Testability: ViewModels can be tested in isolation from the user interface.
Reusability: ViewModels are versatile and can be shared across multiple Views.
Scalability: The clear separation makes large codebases easier to manage.
Conclusion
The MVVM architecture is a natural fit for SwiftUI’s declarative programming style. By separating the responsibilities of Model, View, and ViewModel, we create a clean and maintainable codebase. The employee management app demonstrated in this post is a practical example of how to implement MVVM effectively.
Whether you’re building a small app or a complex application, embracing MVVM can significantly enhance your development experience and the quality of your code.
Top comments (0)