A dumb UI is a good UI: Using MVP in iOS with swift

The Model-View-Controller is a common design pattern when it comes to the development of an iOS application. Usually the view layer consists of elements from UIKit defined programmatically or in xib-files, the model layer contains the business logic of the application and the controller layer, represented by classes of UIViewController, is the glue between model and view.

MVC

The MVC pattern

One good part of this pattern is to have the business logic and business rules encapsulated in the model layer.However, the UIViewController still contains the UI related logic which means things like:

  • calling the business logic and bind the results to the view
  • managing the view elements
  • transforming the data coming from the model layer into a UI friendly format
  • navigation logic
  • managing the UI state
  • and more …

Having all of those responsibilities, ViewControllers often get huge and hard to maintain and to test.

So, it is time to think about improving MVC to deal with those problems. Let’s call this improvement Model-View-Presenter MVP.

The MVP pattern was first introduced in 1996 by Mike Potel and was discussed several times over the years. In his article GUI Architectures Martin Fowler discussed this pattern and compared it with other patterns for managing UI code.
There are many variations of MVP with small differences between them. In this post, i chose the common one that seems to be mostly used in the today’s app development. The characteristics of this variant are:

  • the view part of the MVP consists of both UIViews and UIViewController
  • the view delegates user interactions to the presenter
  • the presenter contains the logic to handle user interactions
  • the presenter communicates with model layer, converts the data to UI friendly format, and updates the view
  • the presenter has no dependencies to UIKit
  • the view is passiv (dump)
MVP

The MVP pattern

The following example will show you how to use MVP in action.

Our example is a very simple application, that displays a simple user list. You can get the complete source code from hier: https://github.com/iyadagha/iOS-mvp-sample .

Let’s start with a simple data model for the user:

struct User {
    let firstName: String
    let lastName: String
    let email: String
    let age: Int
}

Then we implement a simple UserService, that asynchronously returns a list of users:

class UserService {

    //the service delivers mocked data with a delay
    func getUsers(callBack:([User]) -> Void){
        let users = [User(firstName: "Iyad", lastName: "Agha", email: "iyad@test.com", age: 36),
                     User(firstName: "Mila", lastName: "Haward", email: "mila@test.com", age: 24),
                     User(firstName: "Mark", lastName: "Astun", email: "mark@test.com", age: 39)
                    ]

        let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(2 * Double(NSEC_PER_SEC)))
        dispatch_after(delayTime, dispatch_get_main_queue()) {
            callBack(users)
        }
    }
}

The next step ist writing the UserPresenter. First we need a data model of the user, that can be directly used from the view. It contains properly formatted data as needed from the view:

struct UserViewData{   
    let name: String
    let age: String
}

After that, we need an abstraction of the view, which can be used in the presenter without knowing about UIViewController. We do that by defining a protocol UserView:

protocol UserView: NSObjectProtocol {
    func startLoading()
    func finishLoading()
    func setUsers(users: [UserViewData])
    func setEmptyUsers()
}

This protocol will be used in the presenter and will be implemented later from the UIViewController. Basically, the protocol contains functions called in the presenter to control the view.

The presenter itself looks like:

class UserPresenter {
    private let userService:UserService
    weak private var userView : UserView?
    
    init(userService:UserService){
        self.userService = userService
    }
    
    func attachView(view:UserView){
        userView = view
    }
    
    func detachView() {
        userView = nil
    }
    
    func getUsers(){
        self.userView?.startLoading()
        userService.getUsers{ [weak self] users in
            self?.userView?.finishLoading()
            if(users.count == 0){
                self?.userView?.setEmptyUsers()
            }else{
                let mappedUsers = users.map{
                    return UserViewData(name: "\($0.firstName) \($0.lastName)", age: "\($0.age) years")
                }
                self?.userView?.setUsers(mappedUsers)
            }
            
        }
    }
}

The presenter hat the functions attachView(view:UserView) and attachView(view:UserView) to have more control in the UIViewContoller’s life cycle method as we will see later.
Note that converting User to UserViewData is a responsibility of the presenter. Also note that userView must be weak to avoid retain cycle.

The last part of the implementation is the UserViewController:

class UserViewController: UIViewController {

    @IBOutlet weak var emptyView: UIView?
    @IBOutlet weak var tableView: UITableView?
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView?

    private let userPresenter = UserPresenter(userService: UserService())
    private var usersToDisplay = [UserViewData]()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView?.dataSource = self
        activityIndicator?.hidesWhenStopped = true

        userPresenter.attachView(self)
        userPresenter.getUsers()
    }

}

Our ViewController has a tableView to display the user list, an emptyView to display, if no users are available and an activityIndicator to display while the app is loading users. Furthermore, it has a userPresenter and a list of users.

In the viewDidLoad method, the UserViewController attach itself to the presenter. This works because the UserViewController, as we will see soon, implements the UserView protocol.

extension UserViewController: UserView {

    func startLoading() {
        activityIndicator?.startAnimating()
    }

    func finishLoading() {
        activityIndicator?.stopAnimating()
    }

    func setUsers(users: [UserViewData]) {
        usersToDisplay = users
        tableView?.hidden = false
        emptyView?.hidden = true;
        tableView?.reloadData()
    }

    func setEmptyUsers() {
        tableView?.hidden = true
        emptyView?.hidden = false;
    }
}

As we see, these functions contains no complex logic, they are just doing pure view management.

Finally, the UITableViewDataSource implementation is very basic and looks like:

extension UserViewController: UITableViewDataSource {
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return usersToDisplay.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "UserCell")
        let userViewData = usersToDisplay[indexPath.row]
        cell.textLabel?.text = userViewData.name
        cell.detailTextLabel?.text = userViewData.age
        cell.textLabel
        return cell
    }
}

 

MVP-iOS

The end result yeah

Unit testing

One of the benefits of doing MVP is to be able to test the biggest part of the UI logic without testing the UIViewController itself. So if we have a good unit test coverage of our presenter, we don’t need to write unit tests for the UIViewController anymore.

Let’s now have a look on how we can test our UserPresenter. First we define tow mocks to work with. One mock is of the UserService to make it deliver the needed list of users. The other mock is of the UserView to verify if the methods are called properly.

class UserServiceMock: UserService {
    private let users: [User]
    init(users: [User]) {
        self.users = users
    }
    override func getUsers(callBack: ([User]) -> Void) {
        callBack(users)
    }

}

class UserViewMock : NSObject, UserView{
    var setUsersCalled = false
    var setEmptyUsersCalled = false

    func setUsers(users: [UserViewData]) {
        setUsersCalled = true
    }

    func setEmptyUsers() {
        setEmptyUsersCalled = true
    }
}

Now, we can test if the presenter behave correctly when the service delivers a non empty list of users.

class UserPresenterTest: XCTestCase {

    let emptyUsersServiceMock = UserServiceMock(users:[User]())

    let towUsersServiceMock = UserServiceMock(users:[User(firstName: "firstname1", lastName: "lastname1", email: "first@test.com", age: 30),
                                                     User(firstName: "firstname2", lastName: "lastname2", email: "second@test.com", age: 24)])

    func testShouldSetUsers() {
        //given
        let userViewMock = UserViewMock()
        let userPresenterUnderTest = UserPresenter(userService: towUsersServiceMock)
        userPresenterUnderTest.attachView(userViewMock)

        //when
        userPresenterUnderTest.getUsers()

        //verify
        XCTAssertTrue(userViewMock.setUsersCalled)
    }
}

In the same way we can test if the presenter works correctly if the service returns an empty list of users.

func testShouldSetEmptyIfNoUserAvailable() {
        //given
        let userViewMock = UserViewMock()
        let userPresenterUnderTest = UserPresenter(userService: emptyUsersServiceMock)
        userPresenterUnderTest.attachView(userViewMock)

        //when
        userPresenterUnderTest.getUsers()

        //verify
        XCTAssertTrue(userViewMock.setEmptyUsersCalled)
    }

 

Where to go from there

We have seen that MVP is an evolution of MVC. We only need to put the UI logic in an extra component called presenter and make our UIViewController passiv (dump).

One of the characteristics of MVP ist that both presenter and view know each other. The view, in this case the UIViewController, have a reference of the presenter and vice versa.
Though the reference of the view used in presenter could be removed using reactive programing. With reactive frameworks such as ReactiveCocoa or RxSwift it is possible to build an architecture, where only the view knows about the presenter and not vice versa. In this case the architecture would be called MVVM.

If you like to learn more about MVVM in iOS please check the following posts:
MVVM Tutorial with ReactiveCocoa
Implementing MVVM in iOS with RxSwift

12 thoughts on “A dumb UI is a good UI: Using MVP in iOS with swift

  1. Aron Campeau

    Having read this I believed it was very enlightening. I appreciate you finding the time and effort to put this information together. I once again find myself personally spending a lot of time both reading and commenting. But so what, it was still worth it!|

    Reply
  2. Vince O'Sullivan

    Before reading this article I understood the difference between MVP and MVC, but I couldn’t see the advantage because I was unsure where precisely the responsibility lay for updating components on the UIView. The most common examples of MVP use a UITableView for their example and pass a UITableViewCell to the presenter for updating. This did not appear to be a real improvement to me, just shifting the code around.

    However, your example included the line “The presenter has no dependencies to UIKit.”. This is probably the most important line of the whole tutorial. It strongly clarifies the different responsibilities of the Presenter and the UIViewController. The presenter presents data in a view agnostic manner. If the application were to be ported from iOS to macOS, modifications to the Presenter should not be needed.

    Thanks for that clarification.

    Reply
    1. Iyad Agha Post author

      Hi,
      since the presenter has only a weak reference of the View you do not need to call it explicitly. But for clarity reasons i would call it. A good place for it is the dealloc method of the UIViewController

      Reply
  3. Eli

    I have 2 questions:

    1. What about the navigation, who is the responsable?
    2. Is this similar to MVVM? I found the Presenter == ViewModel, because the VM has these things:
    – UI state
    – Services
    – Model
    – Testable, we can test the ui through VM(this contains the state of the UI).

    Regards.

    Reply
    1. Iyad Agha Post author

      Thanks Eli for your good questions.
      1. The pure MVP does not say anything explicitly about the navigation logic. Applying the principles of MVP to navigations means that the presenter should manage the navigation and the view should execute it. There is a different pattern called VIPER which contains a routing component responsible for the navigation.

      2. The MVVM is also a good pattern which addresses a different problem. The main responsibility of the VM is to calculate and prepare the data for the view. The view logic for example when to show an empty screen or when to show an error message remains in the view itself. Using MVP the whole UI related logic is part of a testable presenter.

      Reply
    1. Iyad Agha Post author

      Thank you Oleg for your comment.I consider the MVP as the simplest way to make the UIViewController have less logic. I already read the article ritten by Ryan about VIPER. My problem with this and also with Uncle’ Bob clean architecture is that they seems to me a bit overengineered, and as you said, in a real world app you would produce a lot of boilerplate.

      Reply
      1. Oleg

        Yepp, it’s a double-edged question. And my current feel on VIPER is exact feel of overengineering :/ Especially if you have a nice Services & DI already

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *