MVVM Pattern in Swift: A Step-by-Step Tutorial with Code Examples
Introduction
The MVVM (Model-View-ViewModel) pattern is a design pattern used to separate the user interface (View) from the underlying data (Model) and business logic (ViewModel) of an application. It was first introduced by Microsoft for use with their WPF (Windows Presentation Foundation) framework, but it has since been adopted by other platforms including iOS and Swift.
In this article, we will explore the MVVM pattern in the context of a Swift iOS application, and provide an example implementation to help you better understand how it works.
Model-View-ViewModel
The MVVM pattern consists of three key components:
Model
The data or business logic of the application. This component defines the data and the methods that can be performed on that data.View
The user interface of the application. This component is responsible for displaying the data to the user.ViewModel
The intermediary between the Model and the View. This component is responsible for translating data from the Model into a format that can be displayed by the View. It also contains the business logic and other operations that the View can call to perform actions.Benefits of MVVM
The MVVM pattern has several benefits for Swift iOS applications:
Separation of Concerns
By separating the Model, View, and ViewModel, each component can focus on its own responsibilities without interfering with the others. This makes the code easier to maintain and update.
Testability
Since the ViewModel contains the business logic and operations of the application, it can be easily tested without having to interact with the user interface.
Flexibility
With the ViewModel acting as an intermediary between the Model and View, it is easy to swap out either component without affecting the other. This makes it easier to change the user interface or data layer without having to modify the entire application.
MVVM in Swift
Now that we understand the basic concepts of the MVVM pattern, let's look at an example implementation in Swift.
We will create a simple iOS application that displays a list of users and allows the user to add new users to the list.
Model
struct UserModel { let name: String let age: Int }
View
Next, we will create a UserListViewModel class to act as the ViewModel for our application. This class will contain the business logic for adding new users and retrieving the list of users:
class UserListViewModel { private var userList = [UserModel]() func getUsers() -> [UserModel] { return userList } func addUser(name: String, age: Int) { let newUser = UserModel(name: name, age: age) userList.append(newUser) } }
ViewModel
Finally, we will create a UserListViewController class to act as the View for our application. This class will be responsible for displaying the list of users and allowing the user to add new users:
import UIKit class UserListViewController: UIViewController { @IBOutlet weak var tableView: UITableView! var viewModel: UserListViewModel! override func viewDidLoad() { super.viewDidLoad() title = "User List" // Initialize viewModel viewModel = UserListViewModel() // Set up table view tableView.dataSource = self tableView.delegate = self // Bind to viewModel viewModel.userList.bind { [weak self] _ in self?.tableView.reloadData() } } @IBAction func addUserTapped(_ sender: UIBarButtonItem) { let alert = UIAlertController(title: "Add User", message: nil, preferredStyle: .alert) alert.addTextField { (textField) in textField.placeholder = "Name" } alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) alert.addAction(UIAlertAction(title: "Save", style: .default, handler: { [weak alert, weak self] (_) in guard let name = alert?.textFields?[0].text else { return } self?.viewModel.addUser(name: name) })) present(alert, animated: true, completion: nil) } } extension UserListViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel.userList.value.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) let user = viewModel.userList.value[indexPath.row] cell.textLabel?.text = user.name return cell } } extension UserListViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } }
In the UserListViewController, we first declare a property for the viewModel which we initialize in the viewDidLoad method. We also set up the table view's dataSource and delegate to the UserListViewController instance. We then bind to the userList property of the viewModel using a closure, so that the table view is updated whenever the userList changes.
The addUserTapped method creates an alert to allow the user to add a new user. When the user taps the "Save" button, we extract the name from the text field and call the addUser method on the viewModel.
The UITableViewDataSource methods numberOfRowsInSection and cellForRowAt are implemented to display the list of users in the table view.
The UITableViewDelegate method didSelectRowAt is implemented to deselect the selected row after the user taps on it.
With this implementation, our UserListViewController is completely separated from our data model and business logic. It only interacts with the UserListViewModel through its properties and methods, making our code more modular and easier to maintain.
Wrapping up: Implementing the MVVM pattern in Swift
I hope this article has given you a good understanding of the MVVM pattern and how to implement it in Swift. Remember that the key benefits of this architecture are better separation of concerns, increased testability, and more maintainable code. By using MVVM, you can write applications that are easier to understand and modify as they evolve over time.
댓글
댓글 쓰기