はじめに
MVVMの勉強のアウトプットとして簡単なサンプルを作ったので紹介します。
これ以上ないくらい簡単なサンプルになっているので、RxSwift + MVVM初学者の方向けとなります。(著者も初心者です笑)
作ったもの
サインアップ時のバリデーションを行うサンプルを作りました。
正しい入力でなければSignUpButtonを押せないようにしています。ModelはないのでViewとViewModelのみの実装となります。
準備
前準備として正しいメールアドレスかを判定するメソッドを用意しておきます。
class Validator {
static func isEnableEmail(email: String) -> Bool {
let args = "[A-Z0-9a-z._+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let validation = NSPredicate(format: "SELF MATCHES %@", args)
return validation.evaluate(with: email)
}
}
実装
まずは、ViewModelから実装していきます。
(写経するとデータの流れを把握できると思うので、手を動かして理解することをお勧めします。)
ViewModel
protocol ViewModelInputs {
var email: BehaviorRelay<String> { get }
var password: BehaviorRelay<String> { get }
}
protocol ViewModelOutputs {
var isSignUpButtonEnabled: Driver<Bool> { get }
}
protocol ViewModelType {
var inputs: ViewModelInputs { get }
var outputs: ViewModelOutputs { get }
}
class ViewModel: ViewModelType, ViewModelInputs, ViewModelOutputs {
var inputs: ViewModelInputs { return self }
var outputs: ViewModelOutputs { return self }
//MARK: - Inputs
var email = BehaviorRelay<String>(value: "")
var password = BehaviorRelay<String>(value: "")
//MARK: - Outputs
var isSignUpButtonEnabled: Driver<Bool>
init() {
isSignUpButtonEnabled = Observable.combineLatest(email, password)
.map({ (email, password) -> Bool in
return Validator.isEnableEmail(email: email) && password.count > 5
})
.asDriver(onErrorDriveWith: .empty())
}
}
Inputs
emailとpasswordはViewの入力を受け取るためのプロパティです。
BehaviorRelayは初期値を設定できて、onNextのみ流れるObservableです。
Outputs
isSignUpButtonEnabledをViewからバインドしてSignUpButtonのisEnabledに反映させます。
init()
Outputs
OutputsのisSignUpButtonEnabledに入力結果を評価してBoolに変換したものを格納していきます。
まず、emailとpasswordをcombineLatestで1つのObservableに集約し、mapで(String, String) -> Boolへの変換を行います。
Observableから.asDriver(onErrorDriveWith: .empty())を用いることでDriverへ変換しています。
View
class ViewController: UIViewController {
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordtextField: UITextField!
@IBOutlet weak var signUpButton: UIButton!
private let bag = DisposeBag()
private var viewModel: ViewModelType!
override func viewDidLoad() {
super.viewDidLoad()
viewModel = ViewModel()
emailTextField.rx.text.orEmpty
.bind(to: viewModel.inputs.email)
.disposed(by: bag)
passwordtextField.rx.text.orEmpty
.bind(to: viewModel.inputs.password)
.disposed(by: bag)
viewModel.outputs.isSignUpButtonEnabled
.drive(signUpButton.rx.isEnabled)
.disposed(by: bag)
}
}
emailTextFieldとpasswordTextFieldの入力値をViewModelの入力用プロパティにバインドします。
ViewModelからのOutputであるisSignUpButtonEnabledをsignUpButtonのisEnabledにバインドし、バリデーションの結果を反映させます。
Driverをバインドするときはbindではなくdriveを使用します。
これで完成です!
今後
今回はボタンのisEnabledしか操作していませんが、少し複雑にしたバージョンも作りたいと思っています。
最後まで読んでいただきありがとうございました!