Coordinators, Protocol Oriented Programming and MVVM; Bullet-proof architecture with Swift

Aaron Bikis
11 min readMay 2, 2018

--

The origins of MVVM or Model, View, View Model, comes from two Microsoft developers in 2005 who published the article: Introduction to Model/View/ViewModel pattern for building WPF apps

It fixes several narrative gaps in the standard MVC or Model View Controller paradigm, specifically removing the side effect which creates View Controller that are:
1) Immensely more aware of their connected Models than they should be
2) Become the focal point of the applications logic
3) Grow more responsible than their named task
View Controllers become giant files that are hard to maintain, aren’t modular and are hard to re-purpose.

So enters the View Model. Too take it from the authors on the subject

The term means “Model of a View”, and can be thought of as abstraction of the view, but it also provides a specialization of the Model that the View can use for data-binding. In this latter role the ViewModel contains data-transformers that convert Model types into View types, and it contains Commands the View can use to interact with the Model.

By taking the logic and data mutation and placing it a VM, you can reuse the view’s data across your entire application. Well dive more into that later.

Protocol Oriented Programming. This is concept that is being actively pushed by apple (source), and I could write an entire article on the subject but the take away and how we are going to be using it is fairly simple. In order to create highly reusable View Models with strict confines of functionality we will create protocols that inform how our model is created. Once we have that model we’ll create View Model that stores all of that information and can be passed a generic object to our View Controller.

Coordinators. The term Coordinators was, as far as I can tell, coined by Soroush Khanlou in 2015. I’ve seen lots of flavors of his work pop up by other fantastic developer’s take on the subject. For a bare bones, strictly POP approach check out Niels Van Hoorn, but my favorite so far is Aleksandar Vacić.

Coordinators completely solve the data flow in the UI layer. They don’t hold nor contain any sort of app’s data — their primary concern is to shuffle data from middleware into front-end UI.

What they do:

Create instances of the VCs

Show or hide VCs

Configure VCs (set DI properties)

plus:

Receive data requests from VC

Route requests to middleware

Route results back to VC

He’s taken it a step further with his update to Coordinators which further cements them as the presenters of the View Controller and communicator with the model. If you look at Navigation Coordinator it subclasses UINavigationController.

A Coordinator should be named by the activities it creates and manages and should present and trigger the creation of information for the VCs that contain the UI of those activities.

Today we’ll be creating a simple login application. One that has a LoginViewController a SetupAccountViewController and a ForgotPasswordViewController. These are all narratives that interact with the user’s account information and authentication and the data being fed into and processed from these view controllers is strikingly similar:

1 ) There are forms that accept user data
2 ) This data then need to be validated and processed
3 ) If the data isn’t validated, the user needs to be notified that their information is incorrect
4 ) If the data is validated, then we need to create some network request to authenticate, create a new account for the user, or retrieve the user’s forgotten password

I use a very specific work flow when creating an app and as long as I follow it the complicated nature of following an architecture scheme becomes pretty simplistic. Things have an obvious place.

Here’s a very simplistic graphic on how we’ll be structuring our architecture:

The MVVM / Coordinator Pattern

And here’s a more detailed graphic on our code creation process:

Flow Diagram for creating this pattern

Following this process lets hop into Xcode and make a log in app.

You can download the starter project here

I’ve added a several files to start us off.

First of all we have a App Dependency file that will contain all the various API’s well need for this application. In larger files this would contain things like Data Persistence, Networking, Asset Management and Security Managers. In ours it has our Networking class which to keep the scope of this project down is really a dummy Networking manager which will spit back 200 responses for every call we make. Our Needs Dependency protocol will make sure that we are propagating our dependency throughout our coordinators, keeping state intact using ‘Dependency Injection’ more on that here.

We’ll also be using Swift Validator for our form’s validation, cuz shit’s awesome.

Our user’s will open our app, and be introduced to a setup account page, if they have an account they can login and if they have an account but forgot their password they can request it.
All in all a very simple application and one that requires less code than you might think.

First let’s create the model that will inform the narrative which is our user’s experience. Consider the file AccountType

enum AccountType {
case login, setup, forgotPassword
}

This is the blueprint for the entire app and will be using this all over. Since we have the wire-frame for app, lets create the wire-frame for something more specific to the experience like our form’s model.

Create a file FormTextField :

protocol FormTextField {
var apiKey: String { get }
var placeholder: String { get }
var textColor: UIColor { get }
var validationRules: [Rule] { get }
var keyboardType: UIKeyboardType { get }
}

Pretty standard form field stuff; a placeholder containing the purpose of the field, an API key that we’ll use later, some styling and the validation rules.
Since we want our text fields to look similarly we should probably have them all have the same text color. Instead of listing that in every field’s VM that we create we can create default values for our protocol here by adding an extension:

extension FormTextField {
var keyboardType: UIKeyboardType { return .default }
var textColor: UIColor { return UIColor.lightGray }
var validationRules: [Rule] { return [RequiredRule()] }
}

We’ll also add a default value for the keyboard and make the default validation rule simply required.

Next we’ll create the view model for each text field we want to have in our application. Create a file called FormViewModel and add the following:

private struct UserNameField: FormTextField {
var placeholder: String = "User Name"
var apiKey: String = "user_name"
}

private struct PasswordField: FormTextField {
var placeholder: String = "Password"
var validationRules: [Rule] = [MinLengthRule(length: 9, message: "Passwords need to be a minimum of 9 characters long")]
var apiKey: String = "password"
}

private struct EmailField: FormTextField {
var placeholder: String = "Email Address"
var validationRules: [Rule] = [EmailRule()]
var apiKey: String = "email_field"
}

Now we have a clear, concise and testable road map to our form’s fields. We’ll make this private so that we make sure they can’t be altered. We’ll create the full view model for the form above this code:

typealias FormFields = [FormTextField]

class FormViewModel: NSObject {

private lazy var validator = Validator()
var fields: FormFields {
get {
switch self.type {
case .login:
return [UserNameField(), PasswordField()]

case .setup:
return [EmailField(), UserNameField(), PasswordField()]

case .forgotPassword:
return [EmailField()]
}
}
}

var type: AccountType

init(type: AccountType) {
self.type = type
super.init()
}
func validateForm(){

}
}extension FormViewModel: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

}
}

First we create a typealias for an array of our fields. Notice that our form inherits from UITableViewDataSource. This is most commonly put inside a VC but with MVVM we can have this one class handle the data source for all three of our user’s experiences. We’ll also create an instance of the Validator and create a method that when called will validate the form. We’ll leave that blank for now.

Next up is a simple switch statement on the value we get when we initialize our form’s view model. It will return a different set of Fields based on what form we want.
Then we conform to the UITableView’s data source required methods and we’re most of the way to building our form.

Following our process we’ll now create the UI our view model informs. I’ve already created FormTableViewCell for you so just add the following:

func setView(for field: FormTextField) -> UITextField {
textField.placeholder = field.placeholder
textField.textColor = field.textColor
return textField
}

Back in the FormViewModel let’s update our Tableview’s data source methods to create a cell.

extension FormViewModel:  UITableViewDataSource  {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: FormTableViewCell!
cell = tableView.dequeueReusableCell(withIdentifier: FormTableViewCell.reuseID,
for: indexPath) as? FormTableViewCell
?? FormTableViewCell()

let field = cell!.setView(for: fields[indexPath.row])
validator.registerField(field,
rules: fields[indexPath.row].validationRules)
return cell
}

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

We’ll also register the field for validation.

Now lets propagate the account type through our Coordinator and ViewController so we can set the form dynamically based on the presenting Coordinator’s logic. In AuthenticateUserVC add the datasource variable

var datasource: FormViewModel

And in the init method before super.init() add:

datasource = FormViewModel(type: type)

We’ll require an AccountType on initialization of our VC and create an instance of the view model based on that type. Now connect the datasource to the form inside the UITableView’s lazy init.

tv.dataSource = self.datasource

Let’s take a look at where we’re at. The VC is almost entirely ignorant of the details involving it’s data source. We have a clean presenter, the Account Coord that is also ignorant of what type of account we are currently presenting. It just knows that there is an AccountViewController type currently presented.
Our Application Coordinator now has the jurisdiction on what to present at the beginning of the application. This enables us to quickly test UI by changing the AccountType and therefore changing the UI presented on launch.

Great! Now lets do something with that validated data. We’re now on the third column in our process graphic above.

Add to the top of FormViewModel

typealias ValidationCallback = (Bool, [String]?)

protocol FormViewModelDelegate: class {
func formWasValidated(_ successfully: ValidationCallback)
}

And of course add a reference to the delegate:

var delegate: FormViewModelDelegate?

We’ll send a success or fail message which if failed will include an array of the error messages given back to us from the ValidationDelegate. Speaking of lets conform our view model to that:

extension FormViewModel: ValidationDelegate {
func validationSuccessful() {
delegate?.formWasValidated((true, nil))
}

func validationFailed(_ errors: [(Validatable, ValidationError)]) {
delegate?.formWasValidated((false, errors.map({ $0.1.errorMessage })))
}
}

And lets add the call to validate the form in our helper method that we created before:

func validateForm(){
validator.validate(self)
}

Lastly lets set up our login button to trigger the validation function:

@objc
private func onAcceptButtonTap(){
datasource.validateForm()
}

Our app should compile and load the form. Tap the login in button and you should have a print statement based on your input.

Let’s wire up a delegate to notify a responder that we’ve received and processed input from the user and we have a response for them. Add this to AuthenticateUserVC

extension AuthenticateUserViewController: FormViewModelDelegate {
func formWasValidated(_ successfully: ValidationCallback) {
guard let errorMessage = successfully.1 else {
// no validation errors!
return
}

let message = errorMessage.reduce(into: "", { $0 = "\($0 + "\n" + $1)" })
let alertVC = UIAlertController(title: "Form Invalid",
message: message,
preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "Rodger!",
style: .destructive,
handler: nil))
present(alertVC, animated: true, completion: nil)
}
}

Don’t forget to assign the delegate to the VC. Do this in the init method after super.init() since we’re calling self

datasource.delegate = self

Here we make sure the form was invalid, reduce our error messages into a single message and present it to the user as a alert.

If there aren’t any errors we now need to notify our coordinator that the purpose of this VC has been fulfilled and to update our navigation stack.

Now we’re on the last task of our process, and we need to notify the presenter to update the navigation stack should the responder receive valid user credentials.

protocol AuthenticateUserViewControllerDelegate: class {
func userProvidedValidated(credentials: Credentials, type: AccountType)
}

Credentials is a typealias for [String: Any]
And now let’s iterate through our textfields and our viewModel’s field data for the api keys we should use to create a Credential object add this to the formWasValidated method:

guard let errorMessage = successfully.1 else {
var credentials = Credentials()
for (index, key) in datasource.fields.enumerated() {
guard let cell = form.cellForRow(at: IndexPath(item: index, section: 0)) as? FormTableViewCell else { return }
credentials[key.apiKey] = cell.textField.text
}

delegate?.userProvidedValidated(credentials: credentials, type: datasource.type)
return
}

Let’s assign AccountCoord to the VC’s delegate in the configure method and then conform to that delegate in an extension.

extension AccountCoordinator: AuthenticateUserViewControllerDelegate {
func userProvidedValidated(credentials: Credentials, type: AccountType) {
dependencies?.networking.perform(call: type, with: credentials, callback: { (response) in
switch response {
case ("success", 200):
print("user logged in!")
break
default:
// issue processing request
break
}
})
}
}

That’s the end of the process! We’ve created most of the user’s first narrative of being able to login with their credentials.

Now onto the other two narratives. We’re closer to accomplishing them than you might think.

Let’s add a delegate method for when a user would like to navigate away from the login screen. Add this to AuthenticateUserViewControllerDelegate

func updateResponder(to type: AccountType)

Now conform the AccountCoord by adding the new method.

func updateResponder(to type: AccountType) {
configure(for: type)
}

This will reconfigure the same VC with all three buttons present on it for each user story but with a different form. To get the UI to where we want it, where we only have the one button on SignUp and ForgotPassword should be fairly simple seeing as we’ve already done the heavy lifting.

Since our Login screen’s UI is the most complicated and the only difference between our other two screens is their form. Let’s create a subclass of AuthenticateUserVC for the login process.

Create a file called LoginVC and lets add the UI for the sign up button and forgot password button.

final class LoginViewController: AuthenticateUserViewController {

lazy var signUpButton: UIButton = self.buttonFactory(with: .signup)

lazy var forgotPassButton: UIButton = self.buttonFactory(with: .forgotpass)


init(){
super.init(type: .login)
}
required init?(coder aDecoder: NSCoder) { fatalError() }

override func loadView() {
super.loadView()
signUpButton.bottomAnchor.constraint(equalTo: acceptButton.topAnchor, constant: -15).isActive = true
signUpButton.leadingAnchor.constraint(equalTo: acceptButton.leadingAnchor).isActive = true
signUpButton.trailingAnchor.constraint(equalTo: acceptButton.trailingAnchor).isActive = true
signUpButton.heightAnchor.constraint(equalToConstant: ButtonViewModel.largeButtonHeight).isActive = true

forgotPassButton.topAnchor.constraint(equalTo: form.bottomAnchor, constant: 15).isActive = true
forgotPassButton.leadingAnchor.constraint(equalTo: acceptButton.leadingAnchor).isActive = true
forgotPassButton.trailingAnchor.constraint(equalTo: acceptButton.trailingAnchor).isActive = true
forgotPassButton.heightAnchor.constraint(equalToConstant: ButtonViewModel.largeButtonHeight).isActive = true
}
}

Back in AuthenticateUserViewController add this to onForgotPasswordTap(:

guard datasource.type != .forgotPassword else {
datasource.validateForm()
return
}
delegate?.updateResponder(to: .forgotPassword)

And the same for onSetupTap(:

guard datasource.type != .setup else {
datasource.validateForm()
return
}
delegate?.updateResponder(to: .setup)

If you haven’t really looked at how our buttons are being created yet it is an almost exact process to our form! Since our buttons are created only if they are called during the life cycle of our app due to their lazy initialization, when we add our code over to LoginVC, these buttons will only be created that VC.

All that is left to do is update AccountCoord to create an instance of LoginVC in our createViewController method instead of AuthenticateUserVC

private func createViewController(for type: AccountType) -> AuthenticateUserViewController {
guard type != .login else {
return LoginViewController()
}
return AuthenticateUserViewController(type: type)
}

Now if you run the app you should have a finished user story.

This is a concept in progress and I’d love to hear where you think it may be improved upon! General comments on the concept are absolutely welcome as well.

Extra Credit:

Create an implement a view model for the alert presented to the user on failed validation. Show different messages pending on what screen you are on.

--

--