Swift初学者がViewController に集中しがちなロジックを ViewModel に切り出してみた

## はじめに
初学者はViewControllerをfatにしがちだからViewModelを使って可読性を上げましょうという回です。
安心してください、私も初学者です。笑
どうやらView(ViewController)はUIに関することだけに留めて、データの保持やバリデーション,Api処理などの内部処理はViewModelで管理するほうが好ましいとのこと。

ということで今回はログイン時におけるデータの保持とバリデーション処理をViewModelで行ってみたいと思います。

## パターン① ViewControllerでバリデーション処理するときのコード
```swift:LoginViewController.swift
final class LoginViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        emailTextField.delegate = self
        passwordTextField.delegate = self
    }
    
    @IBAction private func loginButtonTapped(_ sender: Any) {
        // 下記でデータを格納してバリデーション処理を行っているが、これをViewModelで処理させる
        guard let email = emailTextField.text,
              let password = passwordTextField.text,
              isValidAccount(email, password)
        else {
            showAlert("メールアドレスまたはパスワードが有効ではありません")
            return
        }

        // emailTextField.textとpasswordTextField.textがnilじゃない、且つValidationがtrueを返した時、
        // API,画面遷移処理（コード省略）
    }

    func isValidAccount(_ email: String, _ password: String) -> Bool {
        return email.count >= 6 && password.count >= 6  // 条件は適当です 
    }

    // 画面タッチでキーボードを閉じる
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        view.endEditing(true)
    }

    // リターンキーを押したときにキーボードを閉じる
    public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}
```
## パターン② ViewModelでバリデーション処理するときのコード
### LoginViewModelを作成
データの管理とバリデーション処理をViewControllerから移行する

```swift:LoginViewModel.swift
class LoginViewModel {
  // データを格納するための変数を用意
  var email: String?
  var password: String?

  // ログインビューからデータを受取るためのメソッドをつくる
  func setData(_ email: String, _ password: String) {
     self.email = email      // 左辺が上で定義したemail,右辺がログインビューから受取るemail
     self.password = password
  }

  // 受け取ったデータでバリデーション処理をする
  func isLoginValidated() -> Bool {
     guard let email = self.email, let password = self.password,
           isValidAccount(email, password)
     else { return false }

     return true
  }

  func isValidAccount(_ email: String, _ password: String) -> Bool {
     return email.count >= 6 && password.count >= 6  // この程度の条件なら直接ぶち込んでいいかも
  }
}
```
### LoginViewControllerを改修
```swift:LoginViewController.swift
final class LoginViewController: UIViewController, UITextFieldDelegate {
    
    private var loginViewModel: LoginViewModel = LoginViewModel()
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        emailTextField.delegate = self
        passwordTextField.delegate = self
    }
    
    @IBAction private func loginButtonTapped(_ sender: Any) {
        guard loginViewModel.isLoginValidated()  // ViewControllerでデータを保持する必要がなくなった！
        else {
            showAlert("メールアドレスまたはパスワードが有効ではありません")
            return
        }

        // loginViewModel.isLoginValidated()がtrueを返した時、
        // API,画面遷移処理（コード省略）
    }
    
    // 画面タッチでキーボードを閉じる
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        view.endEditing(true)
    }

    // リターンキーを押したときにキーボードを閉じる
    public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

   // キーボードを閉じたときに入力データをビューモデルに渡す
   func textFieldDidEndEditing(_ textField: UITextField) {
        guard let email = emailTextField.text, let password = passwordTextField.text else { return }
        loginViewModel.setData(email, password)
   }
}
```
### LoginViewController作成にあたってのポイント
①最初にLoginViewModelのインスタンスを作成しておく。
　初心者あるある：各メソッド内でインスタンスを作成してしまう。⇒データの授受に失敗します。

```
private var loginViewModel: LoginViewModel = LoginViewModel()
```

②キーボードを閉じたときに呼び出されるメソッドを活用する

```
func textFieldDidEndEditing(_ textField: UITextField) {
   // 呼び出したい処理
}
```

## おわりに
今回の例ではコード量が少なくなったわけではないので初学者には利便性は伝わらないかもしれません。私もまだそこまで実感できていません笑
全体最適を考えられるエンジニアとしての第一歩と考えましょう！