David Cordero

SwiftUI, personal thoughts and Model-View-Presenter

Published on 12 Jun 2019

It was just a few days ago that Apple presented SwiftUI, a brand new framework to create the user interface of our Apps that might eventually replace UIKit in our Apps.

Despite the hype, SwiftUI is still just a baby framework, and it will need patience, love and tender to become a strong and useful adult framework. As we already saw on the first version of Swift, new tools need some iterations and some patterns created by the community until they get really useful in our daily basis.

Fortunately, due to the fact that our Apps need to keep backwards compatibility, we still have at least a couple of years to make this evolution happen.

SwiftUI looks very promising to me, and I am very happy that Apple is taking this step forward. I do think that eventually SwiftUI will mean a before and after in the world of iOS development and we might remember UIKit times as distant as we remember pre-ARC times now.

But I also think that this will take time, and it will not happen with the current version of SwiftUI but with the iterations that we will see over the next months or even years.

Because of that, I am happy about SwiftUI, but not hyped about SwiftUI.

The Hype

It is very easy to be hyped by new technologies, because it is an easy way to blame the old technology about your own errors. People can now say that the code on their Apps is messy just because of being based on UIKit.

“Everything would be better if we had had SwiftUI back in the days” they will say.

Assuming your own errors is not easy, or at least it is harder than blaming old technologies instead. But spoiler alert… if that was the problem, SwiftUI will not solve your problems.

Instead of being hyped I think we should be critic with the new technologies, so that we can evolve them and the way we make use of them, to avoid the errors from the past.

SwiftUI, first impressions from a critical point of view

SwiftUI looks very nice, it looks faster to create views with it and it eliminates all the magic behind those unreadable files (storyboard/xib) that were a pain to deal with, especially when having merge conflicts.

But after watching a few videos, and implementing a few small projects, I started to see that it is very easy to mix up the code that defines your views with the code that handle the business or the routing logic of your Apps.

If you check the examples given by Apple, everything happens in the same context. The same file defines your view but also manages the logic of the App which in some cases even touches the network.

That is fine for an example, and I understand that Apple wants to keep the examples small and easy to follow. But coding real Apps that way will end up with a code that is very hard to maintain.

Because of this, and with the aim to avoid this problem, I have been working in playground mode to see how the pattern Model-View-Presenter could be applied to a project in which the view is defined with SwiftUI.

I also noticed important limitations in the way that routing works in SwiftUI. In fact, at the moment SwiftUI even need a UIViewController (from UIKit) to be hosted. How should we manage more advanced routing logic like the one coming from deeplinks is still a mystery for me.

In the playground project that you will find linked at the end of this post, you can find a naive solution to manage routing from an external Wireframe component.

Model-View-Presenter

Model-view-presenter (MVP) is a software pattern originated in the 90s, that is not specific to any platform or programming language.

As a very brief overview, we can say that MVP consists basically of three components:

Using these three components Model, View and Presenter with a clear definition of their roles we can to separate the business logic from the definition of ours views.

Applying Model-View-Presenter to SwiftUI

For this example project, we are going to implement a Login screen. The view consists of a simple form with the fields username and password, a label showing errors and a login button.

Having that requirement we can design the protocol of our presenter as follows, just a single method that our View will call, when the login button is pressed by the user.

import Foundation

protocol LoginPresenter {
    func loginButtonWasPressed()
}

Having that we can also implement our View using SwiftUI as below:

import SwiftUI

struct LoginView: View {
    
    private var presenter: LoginPresenter?
    
    @ObjectBinding
    private var viewModel: LoginViewModel
    
    init(presenter: LoginPresenter?, viewModel: LoginViewModel) {
        self.presenter = presenter
        self.viewModel = viewModel
    }
    
    // MARK: - View
    
    var body: some View {
        
        NavigationView {
            VStack {
                TextField($viewModel.username, placeholder: Text("username"))
                TextField($viewModel.password, placeholder: Text("password"))
                
                Button(action: loginButtonWasPressed) {
                    Text("Login")
                }
                
                if !viewModel.errorMessage.isEmpty {
                    Text(viewModel.errorMessage)
                        .font(.caption)
                        .color(Color.red)
                        .padding(20)
                }
            }
            .padding()
            .navigationBarTitle(Text("Login"))
        }
    }
    
    // MARK: - Actions
    
    private func loginButtonWasPressed() {
        presenter?.loginButtonWasPressed()
    }
}

As you can see, we added a reference to the presenter as a property in the definition of our login view. Using that property we can communicate to the presenter when the user is pressing the Login button in our View.

Please notice the subtle detail that the method is not called login because that name might indicate a business logic decision, and that is something that the presenter should decide in its internal implementation, it is called loginButtonWasPressed instead, which is an action from the view perspective.

Apart from the presenter, there is another interesting property, that is viewModel. This property is the one that will allow us to command updates from the presenter to the View.

In SwiftUI, the source of truth of the data is very important. The source of truth defines the type of property that we need in our view (@State, @Binding or @ObjectBinding). In this case, the source of truth of our data will be managed externally to the view, by our presenter. Because of this, our view model is defined as @ObjectBinding.

If you find confusing this part, you can find further information about when to use each type of property in this video from WWDC 2019.

Now that we have our View ready, all we need is to implement our presenter, to manage our business logic and to update our View as a result of that.

import UIKit

final class LoginPresenterImp: LoginPresenter {
    
    private var viewModel: LoginViewModel
    
    init(viewModel: LoginViewModel) {
        self.viewModel = viewModel
    }
    
    // MARK: - LoginPresenter
    
    func loginButtonWasPressed() {
        print("> LoginButtonWasPressed")
        
        if viewModel.username == "" || viewModel.password == "" {
            viewModel.errorMessage = "Please provide your username and password"
        }
        else if viewModel.username != "Admin" || viewModel.password != "12345" {
            viewModel.errorMessage = "The provided credentials are not correct"
        }
        else {
            viewModel.errorMessage = ""
            print("Succesfully logged in, navigate to next screen")
        }
        
        viewModel.username = ""
        viewModel.password = ""
    }
}

As you can see, here is were the source of truth of our data really is. That viewModel property in the presenter is our data, and we use it to update our view as a result of our business logic from the method loginButtonWasPressed

And that is all that we need, we got it already. We have a View that only cares about UI implementation details that delegates the business logic to its presenter. We have a Presenter that takes care of the business logic and does not care about View implementation details.

The last part that we need is our view model, that might be implemented as follows:

import SwiftUI
import Combine

final class LoginViewModel: BindableObject {
    
    let didChange = PassthroughSubject<LoginViewModel, Never>()
    
    var username: String = ""
    var password: String = ""
    
    var errorMessage: String = "" {
        didSet {
            didChange.send(self)
        }
    }
}

Show me the code

If you are interested about this concept, and you want to know further details about it, I have published in GitHub the playground project that I used for this proof of concept.

The example consists in an App with two different screens login and logged in, having the business logic to log in the user in the presenter as we saw here. But also delegating the routing logic to a separate Wireframe component.

Conclusion

SwiftUI is a brand new framework, it will take us time to see what we can do with it, and what are the best patterns we should apply to it. SwiftUI can be a big success or a big failure based on these times, so let’s play with it.

So please do not take this post as a “You must use Model-View-Presenter with SwiftUI”, but as an example of applying a software pattern in playground mode. If you are looking for advice, then take it as a “Play with SwiftUI to see what makes sense with it” instead.

Do not take SwiftUI too serious yet, we are in playground times, as a result of this times we will see what makes more sense to use in the future when we start using it to build real Apps.

Feel free to follow me on github, twitter or dcordero.me if you have any further question.