Mvc – Usage of MVVM in iOS

iosmodelmvcmvvmswift-language

I'm an iOS developer and I'm guilty of having Massive View Controllers in my projects so I've been searching for a better way to structure my projects and came across the MVVM (Model-View-ViewModel) architecture. I've been reading a lot of MVVM with iOS and I have a couple of questions. I'll explain my issues with an example.

I have a view controller called LoginViewController.

LoginViewController.swift

import UIKit

class LoginViewController: UIViewController {

    @IBOutlet private var usernameTextField: UITextField!
    @IBOutlet private var passwordTextField: UITextField!

    private let loginViewModel = LoginViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    @IBAction func loginButtonPressed(sender: UIButton) {
        loginViewModel.login()
    }
}

It doesn't have a Model class. But I did create a view model called LoginViewModel to put the validation logic and network calls.

LoginViewModel.swift

import Foundation

class LoginViewModel {

    var username: String?
    var password: String?

    init(username: String? = nil, password: String? = nil) {
        self.username = username
        self.password = password
    }

    func validate() {
        if username == nil || password == nil {
            // Show the user an alert with the error
        }
    }

    func login() {
        // Call the login() method in ApiHandler
        let api = ApiHandler()
        api.login(username!, password: password!, success: { (data) -> Void in
            // Go to the next view controller
        }) { (error) -> Void in
            // Show the user an alert with the error
        }
    }
}
  1. My first question is simply is my MVVM implementation correct? I have this doubt because for example I put the login button's tap event (loginButtonPressed) in the controller. I didn't create a separate view for the login screen because it has only a couple of textfields and a button. Is it acceptable for the controller to have event methods tied to UI elements?

  2. My next question is also about the login button. When the user taps the button, the username and password values should gte passed into the LoginViewModel for validation and if successful, then to the API call. My question how to pass the values to the view model. Should I add two parameters to the login() method and pass them when I call it from the view controller? Or should I declare properties for them in the view model and set their values from the view controller? Which one is acceptable in MVVM?

  3. Take the validate() method in the view model. The user should be notified if either of them are empty. That means after the checking, the result should be returned to the view controller to take necessary actions (show an alert). Same thing with the login() method. Alert the user if the request fails or go to the next view controller if it succeeds. How do I notify the controller of these events from the view model? Is it possible to use binding mechanisms like KVO in cases like this?

  4. What are the other binding mechanisms when using MVVM for iOS? KVO is one. But I read it's not quite suitable for larger projects because it require a lot of boilerplate code (registering/unregistering observers etc). What are other options? I know ReactiveCocoa is a framework used for this but I'm looking to see if there are any other native ones.

All the materials I came across on MVVM on the Internet provided little to no information on these parts I'm looking to clarify, so I'd really appreciate your responses.

Best Answer

I am only just approaching these questions myself but I will give the best answers I can and make some observations.

We are told that iOS' MVC expects us to break our program up into models, views and controllers which link specific views to specific models. Sadly, in practice we will have a number of views and a number of models but only one (View)Controller taking care of all the linkages. This is what leads to massive ViewControllers.

Adopting MVVM in this context means breaking the ViewController up into two parts. The part that deals directly with Views (which stays in the ViewController class proper,) and the part that deals directly with Models (which is moved into the ModelView class/struct.)

This means that only the ViewController should be sending/receiving messages from/to the Views and the only non-view object the VC should be conversing with is the ModelView. While the ModelView should be conversing with all the various Model objects and should never converse with a View.

The above notion of the ViewController/ViewModel split leeds to the following answers to your questions:

  1. No, your MVVM implementation is not correct. Your ViewModel methods have a fundamental flaw. They should not be talking directly with any view objects so they aren't in charge of showing the user any alerts. That's the ViewController's job. The ViewModel also isn't in charge of transitioning between ViewControllers.

  2. The ViewController should not be dealing with any model objects. It should only be conversing with Views and the ModelView. Therefore, the ViewController should not be pulling the username and password out of it's views and passing them to the ViewModel's login function. That would require the ViewController to understand something about the differences between Usernames and Passwords and when it is appropriate to use them.

Instead, the ViewController should inform the ViewModel when changes occur in the username and password fields. It should inform the ViewModel when the login button is pressed. It shouldn't have any knowledge about what model objects the viewModel needs in any particular method.

  1. I would expect the validate() function to return a Bool (better to call it isValid and make it a computed property.) I would expect the login() function to accept two blocks (one for pass and one for fail.) It can communicate what happened by calling the correct block. Another option would be to have the function accept one block as a parameter with a success boolean. So something like this:

-

struct LoginViewModel {

    var username: String = ""
    var password: String = ""

    init(username: String, password: String) {
        self.username = username
        self.password = password
    }

    var isValid: Bool {
        return !username.isEmpty && !password.isEmpty
    }

    func login(callback: (error: NSError?) -> Void) {
        let api = ApiHandler()
        api.login(username, password: password, success: { (data) -> Void in
            callback(error: nil)
            }) { (error) -> Void in
                callback(error: error)
        }
    }

}
  1. In the MVVM paradigm, ReactiveCocoa (and RxSwift is another library) are used to communicate changes in the ModelView back to the ViewController, but let me ask you this... Does your ModelView ever change in any substantive way that your ViewController doesn't know about? So far, I have not been in a situation where any other object is sending messages to my ModelView other than the ViewController it is attached to, so there isn't any way for the ModelView to mutate without the ViewController knowing it's at least possible. Because of this, I generally have an updateUI() function in my ViewController. Every time my ViewController sends a potentially mutating message to the ModelView, it then calls updateUI() which examines the ViewModel and updates the UI accordingly. No need for any tricks.

- Hope this helps you and hopefully my answer will stimulate some discussion on this question.

Related Topic