【Xcode/Swift】RxSwiftを使って簡単なログイン画面のロジックを作ってみる

実装

Storyboard

LoginViewModel.swift (ロジック)

import RxSwift
import RxCocoa

protocol LoginViewModelInputs {
    var id: PublishRelay<String> { get }
    var password: PublishRelay<String> { get }
}

protocol LoginViewModelOutputs {
    var isLoginButtonEnabled: Driver<Bool> { get }
}

protocol LoginViewModelType {
    var inputs: LoginViewModelInputs { get }
    var outputs: LoginViewModelOutputs { get }
}

class LoginViewModel: LoginViewModelType, LoginViewModelInputs, LoginViewModelOutputs {
    // MARK: - Input Sources
    let id = PublishRelay<String>()
    let password = PublishRelay<String>()

    // MARK: - Output Sources
    let isLoginButtonEnabled: Driver<Bool>

    // MARK: - Properties
    var inputs: LoginViewModelInputs { return self }
    var outputs: LoginViewModelOutputs { return self }

    private let _isLoginButtonEnabled = BehaviorRelay<Bool>(value: false)
    private let disposeBag = DisposeBag()

    init() {
        self.isLoginButtonEnabled = _isLoginButtonEnabled.asDriver(onErrorJustReturn: false)
        Observable.combineLatest(id, password)
            .map { !$0.0.isEmpty && !$0.1.isEmpty }
            .bind(to: _isLoginButtonEnabled)
            .disposed(by: disposeBag)
    }
}

HomeViewController.swift (メインのView)

import UIKit
import RxSwift
import RxCocoa

final class HomeViewController: UIViewController {

    // MARK: - Properties
    @IBOutlet private weak var idTextField: UITextField!
    @IBOutlet private weak var passwordTextField: UITextField!
    @IBOutlet private weak var loginButton: UIButton!

    private let viewModel: LoginViewModelType = LoginViewModel()
    private let disposeBag = DisposeBag()
    
    // MARK: - View Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        bind()
    }

}

// MARK: - Bindings
private extension HomeViewController {
    func bind() {
        idTextField.rx.text.orEmpty
            .bind(to: viewModel.inputs.id)
            .disposed(by: disposeBag)
        passwordTextField.rx.text.orEmpty
            .bind(to: viewModel.inputs.password)
            .disposed(by: disposeBag)

        viewModel.outputs.isLoginButtonEnabled
            .drive { [weak self] isButtonEnabled in
                self?.loginButton.backgroundColor = isButtonEnabled ? UIColor.systemIndigo : UIColor.lightGray
            }
            .disposed(by: disposeBag)
    }
}