Envative Insights

By: Scott Terry

Publish Date: Friday 1/18/2019


This is the first in a series of articles about building our own mobile framework, the decisions we’ve made along the way and our rationale behind them.

Most common mobile development follows the MVC (Model-View-Controller) pattern as that is the path the native SDK’s lead you down and what is most familiar to the majority of developers.

By contrast, Envative has been making the push to MVVM (Model-View-View-Model) after seeing some great benefits from early forays into using the pattern. We have chosen this pattern for both our iOS and Android platforms. 

Let’s start with a little bit of context.

What is MVC?

MVC framework separates your application into 3 different layers. These layers are the Model, the View and the Controller. The model layer is responsible for the data and business layer of the application and what properties are contained there. The view layer is responsible for the individual UI components that will make up the visual aspect of the application. Finally, the controller acts as the intermediary between the model and view layers, managing what properties from the model layer is passed and displayed in the view layer. A visual representation of how this works can be seen below:

When developing for iOS this turns into the following with the use of UIViewController:

This pattern is so popular because it is simple and easy to pick up, however it suffers from the problem of creating massive view controllers. Since the crutch of the pattern relies on the controller to do most of the heavy lifting it is where the majority of your applications code will end up. This leads to view controllers with 100’s to thousands of lines of code making them harder to organize and maintain.

MVVM to the Rescue!

MVVM, while similar to MVC, helps to reduce massive view controllers by it’s different approach to architecting an application. In MVVM the View and Controller layers become just the View layer responsible for the UI components. The ViewModel layer handles the logic for what to present from a model and handles the view interactions with the model. This separation allows for the business logic code to now live in the ViewModel and leave the view layer only responsible for driving the visual aspect of the UI. This better organizes the code and reduces the code bloat that could occur. In order to follow this pattern you must implement these strict rules:

  1. The View has a reference to the ViewModel, the ViewModel has no reference to the view.
  2. The ViewModel has a reference to the model, the model has no reference to the ViewModel.

A visual representation of how this pattern works is shown below:

So What will MVVM do for me?

  1. More Maintainable - MVVM will help improve code maintenance by reducing code file lengths and help better structure and organize your applications code base.
  2. Improved Testability - With the MVVM pattern you now have the ability to create more granular pieces to your testing suite. This enables a much improved ability to quickly pinpoint where issues may be occurring.
  3. Flexibility - MVVM allows you to quickly replace or reuse different pieces of code more efficiently than MVC.

A Simple Demo App

Now that we’ve learned what the differences between MVC and MVVM are, let’s create a simple iOS app to highlight these differences.
This application will:

  1. Fetch a list of users from a web service API.
  2. Display the users in a list with their name, title and if they are favorited.
  3. Users can be favorited or removed from favorites by tapping on their favorite status icon.

For this application we’ll start with creating the UserModel, Here’s what that struct will look like:

struct UserModel {
    var id:Int
    var name:String
    var roleTitle:String
    var isFavorite:Bool
}

We will now create a UsersViewController containing a UITableView. The viewWillAppear event will fetch the users through, for now, a dummy WebService class that will just return the array of users. This will populate the users and reload our table view.


override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    WebService.shared.fetchUsersList { [weak self] (users) in
        self?.users = users
        self?.tableView.reloadData()
    }
}

Then in order to populate our UserCell we will bind our data to the view in UITableViewDataSource method cellForRowAt


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let user:UserModel = users[indexPath.row]
    let cell:UserCell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell
    
    cell.nameLabel.text = user.name
    cell.roleLabel.text = user.roleTitle
    
    return cell
}

To bind the favorite button we will create a helper function we can call for each cell to bind our touch event and set the proper styling and title:


fileprivate func updateFavoriteButtonTitle(_ button:UIButton, fromUser user:UserModel, atIndexPath indexPath:IndexPath) {
    button.setTitle(user.isFavorite ? "Unfavorite" : "Add to Favorites", for: .normal)
    button.setTitleColor(user.isFavorite ? UIColor.red : UIColor.blue, for: .normal)
    button.tag = indexPath.row
    button.addTarget(self, action: #selector(favoriteButtonWasTapped(_:)), for: .touchUpInside)
}

@objc func favoriteButtonWasTapped(_ sender:UIButton) {
    if users.count > sender.tag {
        users[sender.tag].isFavorite = !users[sender.tag].isFavorite
        tableView.reloadData()
    }
}

That’s it for our sample application let’s take a look at what it looks like:

Moving our app to MVVM

We’ll first start by creating the view model to use for populating our UserCell’s. We’ll provide accessor functions to get the data we want to populate, this way if we want to do any data processing we can do it here. We will also provide a function to modify our model’s isFavorite flag.


class UserCellViewModel: NSObject {
    var user:UserModel!
    var notifyUpdate:(()->Void)?
    
    var nameText:String {
        return user.name
    }
    
    var roleTitleText:String {
        return user.roleTitle
    }
    
    var isFavorite:Bool {
        return user.isFavorite
    }
    
    init(_ model:UserModel, notifyUpdate notify:@escaping ()->Void) {
        super.init()
        
        user = model
        notifyUpdate = notify
    }
    
    func toggleIsFavorite() {
        user.isFavorite = !user.isFavorite
        notifyUpdate?()
    }
}

You will also want to note we’ve added a closure that’s required in the initializer to notify updates. This closure will be used to notify the owning class that we’ve made changes and would like to update our view.

Moving on our next step in moving to MVVM is creating the ViewModel for our TableView. We’ll create a simple one that will provide methods to call from the UITableViewDataSource calls we need to populate our list:


class UserTableViewModel: NSObject {
    fileprivate var cellViewModels:[UserCellViewModel] = []
    var notifyUpdate:(()->Void)?
    
    init(notifyUpdate notify:@escaping ()->Void) {
        super.init()
        
        notifyUpdate = notify
        
        WebService.shared.fetchUsersList { [weak self] (users) in
            self?.processUsers(users)
            self?.notifyUpdate?()
        }
    }
    
    fileprivate func processUsers(_ users:[UserModel]) {
        cellViewModels = users.map{ UserCellViewModel($0, notifyUpdate: { [weak self] () in
            
            self?.notifyUpdate?()
        })}
    }
    
    func numberOfSections() -> Int {
        return 1
    }
    
    func numberOfRowsInSection(_ section:Int) -> Int {
        return cellViewModels.count
    }
    
    func viewModelAt(_ indexPath:IndexPath) -> UserCellViewModel {
        return cellViewModels[indexPath.row]
    }
}

Now that we’ve created our ViewModels lets update our views to conform better to our pattern. First we’ll start small and move the cell population to the UserCell class, our new class will look something like:


class UserCell: UITableViewCell {

    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var roleLabel: UILabel!
    @IBOutlet weak var favoriteButton: UIButton!
    
    var cellModel:UserCellViewModel! {
        didSet {
            updateView()
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        favoriteButton.addTarget(self, action: #selector(favoriteButtonWasTapped(_:)), for: .touchUpInside)
    }
    
    //MARK: - Data Binding Helpers
    func updateView() {
        nameLabel.text = cellModel.nameText
        roleLabel.text = cellModel.roleTitleText
        favoriteButton.setTitle(cellModel.isFavorite ? "Unfavorite" : "Add to Favorites", for: .normal)
        favoriteButton.setTitleColor(cellModel.isFavorite ? UIColor.red : UIColor.blue, for: .normal)
    }
    
    //MARK: - UI Interactions
    @objc func favoriteButtonWasTapped(_ sender:UIButton) {
        cellModel.toggleIsFavorite()
    }
}

Next we’ll update our ViewController to have a property for the viewModel:


var tableViewModel:UserTableViewModel!

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    tableViewModel = UserTableViewModel(notifyUpdate: { [weak self] () in
        self?.updateTableView()
    })
}

And update the UITableViewDataSource calls to use our newly created ViewModels:


func numberOfSections(in tableView: UITableView) -> Int {
    return tableViewModel.numberOfSections()
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tableViewModel.numberOfRowsInSection(section)
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell:UserCell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell
    
    let cellModel:UserCellViewModel = tableViewModel.viewModelAt(indexPath)
    cellModel.notifyUpdate = { [weak self] () in
        self?.updateTableView()
    }
    
    cell.cellModel = cellModel
    
    return cell
}

That’s it! We now have a functioning simple MVVM application.

Where to go from here?

For this simple application we only saved a few lines of code in our view controller, however our code is much more structured. The next step to go from here, which we have already done for our internal framework here at Envative, is to make this approach more generic. We can take our UserTableViewModel and create a protocol or base class from that, along with the UserCellViewModel. This alone will give you a lot of reusability, however, we can go even further. First, by having cell view models have what types of cell they’d prefer to be rendered in, then creating a base table view controller that will then register cell reuse identifiers based on what cell model types are going to be used in that list. These changes will allow the creation of future list controllers to become just assigning a TableViewModel to a generic TableViewController class.

These changes have allowed us to turn list views into a simple task that takes minutes to complete. By removing much of the boilerplate driven code bloat, we can better focus on the custom business logic that matters most to our clients.

If you would like to look at the code yourself you can download the two examples created for this article here on github.



Scott Terry

About the Author:

Scott Terry

Scott is the Director of Mobile Application Development at Envative. He is an expert in mobile and web technologies and has an expert-level understanding of the .NET Framework including C# and JavaScript.  Scott brings infinite value in design and database architecture experience to all the projects he serves. He enjoys studying and researching new technologies and prides himself on staying on the cutting edge of the latest advancements in our industry. Scott has expertise in OAuth, jQuery, JSON, C#, HTML5, Microsoft .Net Framework 4.5, JavaScript, ASP.NET MVC, ReST, Amazon Web Services, SWIFT, Web API and Git.

SMARTER SOFTWARE SOLUTIONS

50 UNIVERSITY AVE | ROCHESTER, NY 14605 | T: 585.327.5640 | F: 585.327.5649 | TF: 855.368.2848

COPYRIGHT © 2019 ENVATIVE. ALL RIGHTS RESERVED. PRIVACY POLICY. Sitemap