DEV Community

Cover image for Understanding MVVM Architecture with SwiftUI
Tech Tales
Tech Tales

Posted on

Understanding MVVM Architecture with SwiftUI

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
}

Enter fullscreen mode Exit fullscreen mode

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)
    }

}

Enter fullscreen mode Exit fullscreen mode

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
    }
}

Enter fullscreen mode Exit fullscreen mode

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)
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

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()
    }
}

Enter fullscreen mode Exit fullscreen mode

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.

Billboard image

Monitoring as code

With Checkly, you can use Playwright tests and Javascript to monitor end-to-end scenarios in your NextJS, Astro, Remix, or other application.

Get started now!

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay