A Dumb UI Is a Good UI: Using MVP In iOS with Swift

Scout24
Scout24 Engineering
6 min readNov 17, 2016

written by Mohamed Iyad Tamer Agha

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.

The MVC pattern
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 are 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 passive (dump)
MVP pattern
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 here: https://github.com/iyadagha/iOS-mvp-sample .

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

[js]
struct User {
let firstName: String
let lastName: String
let email: String
let age: Int
}
[/js]

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

[js]
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)
}
}
}
[/js]

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:

[js]
struct UserViewData{
let name: String
let age: String
}
[/js]

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:

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

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:

[js]
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)
}

}
}
}

[/js]

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:

[js]
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()
}

}
[/js]

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 attaches itself to the presenter. This works because the UserViewController, as we will see soon, implements the UserView protocol.

[js]
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;
}
}
[/js]

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

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

[js]
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
}
}
[/js]

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.

Now let’s 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.

[js]
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
}
}
[/js]

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

[js]
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)
}
}

[/js]

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

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

//when
userPresenterUnderTest.getUsers()

//verify
XCTAssertTrue(userViewMock.setEmptyUsersCalled)
}

[/js]

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, has 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

--

--

Scout24
Scout24 Engineering

With our digital marketplace @ImmobilienScout, we inspire people's best decisions in housing. We make hard decisions easy. https://www.scout24.com