This article is a summary of my reading of Raywenderlich.com’s Design Patterns by Tutorials. The code in this article was modified according to my own ideas after READING the book. If you want to see the original book, please click the link to buy it.
MVVM is a structural design pattern that consists of three main parts:
- Model: Holds application data, usually struct or class.
- View: Displays the application interface, usually inherited from
UIView
. - View Model: Converts Model data into data that can be displayed on the View, usually of class type.
When to use
This is used when we need to convert Model data to be displayed in the View. For example, convert Date to a String. If we were using MVC, we would have to write the transformation code to the View Controller, so the View Controller would have too much code, and that would be what we call a Massive View Controller.
A simple Demo
Let’s assume that to display a list of users that the current user is following, use MVVM mode. The Model is the User; ViewModel is UserListViewModel; UserListViewController is responsible for managing views.
User
struct UsersResults: Codable {
let users: [User]}struct User: Codable {
var userID: String?
var username: String?
}
Copy the code
Users follow Codable; UsersResults is used to assist resolution servers in returning user data, also following Codable. Learn more about Codable areas on your own.
UserListViewModel
final class UserListViewModel {
private var users: [User] = []
// MARK: - Data
func numberOfRows(a) -> Int {
return users.count
}
func user(at index: Int) -> User? {
guard index < users.count else { return nil }
return users[index]
}
// MARK: - Fetching
private let decoder = JSONDecoder(a)func fetchFollowings(userID: String.completion: (() - >Void)? = nil) {
let url = "https://api.your_app_name.com/api/v1/users/\(userID)/followings"
RequestManager.request(.get, urlString: url) { [weak self] (result) in
switch result {
case .success(let data):
guard let jsonData = data,
let result = try? self?.decoder.decode(UsersResults.self, from: jsonData),
let users = result.users else {
self?.users = []
return
}
self?.users = users
case .failure(let error):
self?.users =[]}}}}Copy the code
As mentioned above, the View Model wants to convert the Model’s data into data that can be displayed on the View, so the View Model prepares whatever data the View wants.
The first thing to do is get the data from the server, so I write a fetchFollowings method. RequestManager is wrapped by myself. The last argument to the request method is closure, which will be called after the request completes. Closure contains an enumeration result: 1).success(Data?). If the request is successful and there is data, we parse it to [User]; 2) .fail(Error?) : The request failed.
To display a list of users, usually with a UITableView, we need to tell the UITableView how many rows there are, and each row corresponds to a User, so there are numberOfRows() and User (at index: Int) methods.
UserListViewController
final class UserListViewController: UIViewController {
private let viewModel: UserListViewModel
// MARK: - Initializers
init(withViewModel vm: UserListViewModel) {
viewModel = vm
super.init(nibName: nil, bundle: nil)}required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")}// MARK: - Views
private lazy var tableView: UITableView = {
let tv = UITableView(frame: .zero)
tv.dataSource = self
tv.register(UITableViewCell.self, forCellReuseIdentifier: "UserCell")
return tv
}()
// MARK: - View LifeCycles
override func loadView(a) {
view = UIView()
view.backgroundColor = .white
addTableView()
}
override func viewDidLoad(a) {
super.viewDidLoad()
let userID = "xxxxxxxxxxxxxx"
viewModel.fetchFollowings(userID: userID) { [weak self] in
self?.tableView.reloadData()
}
}
override func didReceiveMemoryWarning(a) {
super.didReceiveMemoryWarning()
}
}
// MARK: - UITableViewDataSource
extension UserListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView.numberOfRowsInSection section: Int) -> Int {
return viewModel.numberOfRows()
}
func tableView(_ tableView: UITableView.cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath)
let user = viewModel.user(at: indexPath.row)
cell.textLabel?.text = user?.username
return cell
}
}
// MARK: - Setup Subviews
extension UserListViewController {
private func addTableView(a) {
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
Copy the code
In MVVM mode, we will let the View Controller hold a View Model, which is initialized by the initialization function; Add UITableView to loadView(); In the implementation of UITableViewDataSource, we directly use the View Model to tell the TableView the data needed; So in viewDidLoad, we get the data from the View Model, and when the request is done, we refresh the TableView.
conclusion
Using the MVVM pattern, we moved a lot of code to the View Model, greatly reducing the burden on the View Controller. Let the View Model tell the View Controller what data it needs. Ideal for use in larger applications. If you are learning application development for the first time, it is advisable to start with MVC.