🩰yawacom - 🐥画面を作っていく

2021/02/26現在編集中

# ログイン画面の作成
1つ前の記事でユーザ登録画面を作った要領でログイン画面も作っていきます．
下記のような流れになるように作っていきます．
今回はおおまかに3つ
💡テキストボックスとボタンで構成されるログイン画面を作る
💡ログイン画面から新規登録画面に遷移するボタンがある
💡ログインしたらやわらかさを記入する画面に遷移する
これらの動きを持つ画面を作ります！

### 💡テキストボックスとボタンで構成されるログイン画面を作る
まずはユーザ名・パスワードを打つためのテキストボックス2つとログインボタンを作ります．一つ前の記事の要領でとりあえずviewDidLoadにぶちこんでます🙇🏻‍♂️
またtextFieldShouldReturnを使用して，テキストボックスに文字を入力して改行を押したらキーボードが隠れるようにします．これをしないとユーザー名などを打ち込んでもキーボードがしまえずログインボタンが押せないハメになります．

```LoginViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        print("ログインビューコントローラー")
        
        self.viewModel = LoginViewModel()
        
        self.view.backgroundColor = .white
        self.userName.backgroundColor = UIColor(named: "textbox")
        self.password.backgroundColor = UIColor(named: "textbox")
        self.loginButton.backgroundColor = .systemGray
        self.loginButton.tintColor = .white
        self.loginButton.layer.cornerRadius = 10
        
        // textFiel の情報を受け取るための delegate を設定するとtextFieldShouldReturnとかが呼ばれる
        self.userName.delegate = self
        self.password.delegate = self
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            // キーボードを閉じる
            textField.resignFirstResponder()
            return true
    }
```

するとこんな感じの画面が作れます．
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149433/1b76c70f-3974-97e3-ef0b-88f476c8719d.png" width=30%>


### 💡ログイン画面から新規登録画面に遷移する
ログインボタンの下にある『新規登録する』（registraionButton）を押すと新規登録画面に遷移するようにします．
流れとしては
1. LoginViewController で registraionButton の input（onTapRegistraionButton） を作ってTap判定する
2. LoginViewModel で input の registraionButton を showRegistraionView にいれる
3. LoginViewModel の output に showRegistraionView をいれる
4. LoginViewController の output.showRegistraionView で遷移先のVCをstartする
のようになります．

#### 1. LoginViewController で registraionButton の input（onTapRegistraionButton） を作ってTap判定する
とりあえずLoginViewModelのファイルだけ生成します．ファイルの新規作成をして中身は下記のように用意しておきます．

```LoginViewModel.swift
import Foundation
import RxSwift
import RxCocoa

class LoginViewModel: ViewModelType {
    private let bag = DisposeBag()

    func transform(input: Input) -> Output {
    }

    struct Input {
        let onTapRegistraionButton: Signal<Void>    // onTapRegistraionButtonをTapしたかをいれる
    }

    struct Output {
        let showRegistraionView: Driver<Void>    // 新規登録画面に遷移するかどうかをいれる
    }
}
```

次にLoginViewControllerでLoginViewModelを宣言し，viewDidLoadでLoginViewModelのtransform関数を呼ぶようにします．

```LoginViewController.swift
    private var viewModel: LoginViewModel!

    override func viewDidLoad() {
        super.viewDidLoad()

        ...略

        let input = createInput()
        let output = viewModel.transform(input: input)
        setupOutput(output)
    }
```

さて中身を書いていきます❣️

LoginViewControllerで『registraionButtonを押した！』という判定をします．ここではRxSwiftの`rx.tap.asSignal()`を使用します．

```LoginViewController.swift
private func createInput() -> LoginViewModel.Input {
        return LoginViewModel.Input(
            onTapRegistraionButton: self.registraionButton.rx.tap.asSignal()
        )
    }
```

#### 2. LoginViewModel で input の registraionButton を showRegistraionView にいれる
1のLoginViewControllerでTapしたかどうかを`.emit`で監視します👀
『押された！』ってなったらshowRegistraionViewをacceptします．

```LoginViewModel.swift
    func transform(input: Input) -> Output {
        let showRegistraionView = PublishRelay<Void>()
        
        input.onTapRegistraionButton
            .emit(onNext: {
                showRegistraionView.accept(())
            })
            .disposed(by: self.bag)
        
        return LoginViewModel.Output(
            showRegistraionView: showRegistraionView.asDriverOnErrorJustComplete()
        )
    }
```

#### 3. LoginViewModel の output に showRegistraionView をいれる
2でacceptされたらOutputにいれることで『新規登録画面に遷移していいよ！』ってことを知らせます．
2のコードの

```LoginViewModel.swift
        return LoginViewModel.Output(
            showRegistraionView: showRegistraionView.asDriverOnErrorJustComplete()
        )
```
ここの部分です．

#### 4. LoginViewController の output.showRegistraionView で遷移先のVCをstartする
3のOutputでLoginViewModelから『新規登録画面に遷移していいよ！』と送られてきたかどうかを`.drive(onNext:`で監視します👀
送られてきたら前の記事の要領で`RegistrationViewController.start(self)`で画面遷移します！

```LoginViewController.swift
    private func setupOutput(_ output: LoginViewModel.Output) {
        output.showRegistraionView
            .drive(onNext: { [unowned self] in
                RegistrationViewController.start(self)
            }).disposed(by: bag)
    }
```

以上で新規登録画面に遷移ができます！
前の記事から新規登録画面の見た目をすこし変えてしまいましたが，動きはこんな感じになります．
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149433/bcd149c1-63af-5f5f-176c-83a2727f9fde.gif" width=30%>


### 💡ログインしたらやわらかさを記入する画面に遷移する

途中です🙇🏻‍♂️

# 詰まったところ
```LoginViewController.swift
    private let viewModel: LoginViewModel

    init(viewModel: LoginViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable)
    required init?(coder _: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
```

このように書くとstartのところの

```LoginViewController.swift
let nextVC = LoginViewController(nibName: "LoginView", bundle: nil)
```
で`Type of expression is ambiguous without more context`というエラーになってしまう．
なので今回はinitを省略しvarなどの宣言には`!`をつけています．（`!`がないと'LoginViewController' has no initializersになる）