2

This question might seem basic, yet I am posting it to get suggestions.

Following is a sample login module using MVVM pattern.

The viewcontroller code is as follows.

class ViewController: UIViewController {

    private var loginviewmodel = LoginViewModel()

    @IBOutlet weak var textFieldUserName: UITextField!

    @IBOutlet weak var textFieldPassword: UITextField!

    @IBAction func signIn(_ sender: Any) {
        //CASE 1
        loginviewmodel.performLogin(name: textFieldUserName.text!, pwd: textFieldPassword.text!)
        //CASE 2
        //loginviewmodel.performLogin()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        textFieldUserName.delegate = self
        textFieldPassword.delegate = self
    }
}

extension ViewController: UITextFieldDelegate {

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        let inputText = (textField.text! as NSString).replacingCharacters(in: range, with: string)

        switch textField {
        case textFieldUserName:
            loginviewmodel.updateUsername(inputText: inputText)
        case textFieldPassword:
            loginviewmodel.updatePassword(inputText: inputText)
        default:
            return false
        }      

        return true
    }
}

And the viewmodel code is as follows.

class LoginViewModel {

    var userName: String?
    var password: String?

    func updateUsername(inputText: String) {
        self.userName = inputText
    }

    func updatePassword(inputText: String) {
        self.password = inputText
    }

    func performLogin() {
        print("Login successful with username = \(userName) and password = \(password).")
    }

    func performLogin(name: String, pwd: String) {
        print("Login successful with username = \(name) and password = \(pwd).")
    }

}

I have two cases in which the values get passed from viewcontroller to viewmodel differently.

  • The first case in which the texts are passed directly as function parameters

  • The second case, in which the texts are passed via text delegate method

Which would be the preferred way here?

Sujal
  • 1,447
  • 19
  • 34

3 Answers3

2

Preferred one is to use delegate method

A common part of a programmer’s job is to keep the UI state synchronized with the Model state. and user Input . when user interact with screen . this interaction should be reflected immediately if it useful for user don't wait until he make another step press submit Button

Let us explain this Assume you have screen with 2 inputs , username or email and password as TextFields

and one button for login

  • you not need user press Login button before enter his username, password
  • you need to notify viewController that user enter data and now he can submit
  • you need to make some validation on user input data before allow submit .for example need validation on number of character for password and email validation ...etc

    First approach without Delegate

    No validation will done just we will inform user error message after he press login , LoginView Model no nothing about ViewController while user typing , viewModel only know information when user press Login Button

Second approach with Delegate

In this approach LoginViewModel now know what is user typing before he press on login Button , we can perform some validations to enable or disable loginButton

ViewController:

class ViewController: UIViewController,LoginViewModelViewDelegate {

    private var loginviewmodel = LoginViewModel()

    @IBOutlet weak var textFieldUserName: UITextField!

    @IBOutlet weak var textFieldPassword: UITextField!

    @IBAction func signIn(_ sender: Any) {

        loginviewmodel.performLogin()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
            // delegate to allow ViewModel notify his view

            loginviewmodel.viewDelegate = self

          self.textFieldUserName.addTarget(self, action: #selector(userNameFieldDidChange(_:)), for: UIControlEvents.editingChanged)
        self.textFieldPassword.addTarget(self, action: #selector(passwordFieldDidChange(_:)), for: UIControlEvents.editingChanged)
    }

  // MARK: - user Input notification


    @objc func userNameFieldDidChange(_ textField: UITextField)
         {
           if let text = textField.text {
               loginviewmodel.userName = text
           }
         }

    @objc func passwordFieldDidChange(_ textField: UITextField)
       {
           if let text = textField.text {
               loginviewmodel.password = text
          }
       }

    // MARK: - LoginViewModel Delegate
    func canSubmitStatusDidChange(_ viewModel: LoginViewModel, status: Bool) {

        // Enable or disable login button to allow user to submit input
    }

}

ViewModel:

import Foundation

protocol LoginViewModelViewDelegate: class
{
    func canSubmitStatusDidChange(_ viewModel: LoginViewModel, status: Bool)
}

class LoginViewModel {

    weak var viewDelegate: LoginViewModelViewDelegate?


    fileprivate var passwordIsValidFormat: Bool = false
    fileprivate var userNameIsValidFormat: Bool = false

    /// Submit
    var canSubmit: Bool {
        return userNameIsValidFormat && passwordIsValidFormat
    }


    /// Email
    var userName: String = "" {
        didSet {
            if oldValue != userName {

                let oldCanSubmit = canSubmit
                userNameIsValidFormat = validateUserNameAsEmailFormat(userName)
                if canSubmit != oldCanSubmit {
                   viewDelegate?.canSubmitStatusDidChange(self, status: canSubmit)
                }
            }
        }
    }



    /// Password
    var password: String = "" {
        didSet {
            if oldValue != password {
                let oldCanSubmit = canSubmit
                passwordIsValidFormat = validatePasswordFormat(password)
                if canSubmit != oldCanSubmit {
                    viewDelegate?.canSubmitStatusDidChange(self, status: canSubmit)
                }
            }
        }
    }

    func performLogin() {
        // perform Login and you can add anather delegate to notify View with error Message of login thow error
    }




    fileprivate func validateUserNameAsEmailFormat(_ userName: String) -> Bool
    {
        let REGEX: String
        REGEX = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,32}"
        return NSPredicate(format: "SELF MATCHES %@", REGEX).evaluate(with: userName)
    }


    /// Validate password is at least 6 characters
    fileprivate func validatePasswordFormat(_ password: String) -> Bool
    {
        let trimmedString = password.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
        return trimmedString.count > 8
    }


}
Abdelahad Darwish
  • 5,969
  • 1
  • 17
  • 35
  • I am thinking of a scenario: Suppose I have a module say transfer money. The module includes 2 viewcontrollers : VCA and VCB. VCA takes inputs like receiver's email, amount etc and VCB takes the user password to finalize the transfer. For both VCs, I use a single VM (validates inputs for both VCs) which is shared through segue. Now all the delegate functions need to be implemented in both VCs, which is unnecessary. So is passing the VM object between VCs not preferred while using protocols and delegates? – Sujal Jun 05 '18 at 03:54
1

I would prefer the second case

loginviewmodel.performLogin()

by using the delegate you can also validate the input in both the textfields as the user is typing instead of waiting the user entering wrong data then validate

Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
0

Please use this:

class ViewController: UIViewController {
    private var loginviewmodel = LoginViewModel()

    @IBOutlet weak var textFieldUserName: UITextField!
    @IBOutlet weak var textFieldPassword: UITextField!

    @IBAction func signIn(_ sender: Any) {
        //CASE 1 
        // Check validation textfield is empty or not  
        loginviewmodel.performLogin(name: textFieldUserName.text!, pwd: textFieldPassword.text!)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        textFieldUserName.delegate = self
        textFieldPassword.delegate = self
    }
}

class LoginViewModel {
    var userName: String?
    var password: String?

    func performLogin(name: String, pwd: String) {
        print("Login successful with username = \(name) and password = \(pwd).")
    }
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Hardik Bar
  • 1,660
  • 18
  • 27