リアクティブプログラミングを用いたビューモデルのI/Oアプローチ
40796 ワード
私が反応プログラミングでMVVMアーキテクチャを使用し始めた時から、私は私のニーズとより魅力的に合う類似した建築を捜していました.私は1つを見つけました、そして、それが完全にそれを適応しなかったとしても、それはKickstarterからあります.
以下は私が今使っているもののサンプルです.私は、ユーザーからの入力データの検証に焦点を当てフォームアプリケーションで簡単な記号を作成しました.
ところで、私はRXSWIFTを反応部分とSnapKitのために使用しています.始めましょう!
ご覧のように、3つのプロトコルがあります.
入力-主にビューコントローラからの操作またはどこにこれを必要とします.
ご覧のように、IsEmailValidとIssuasswordValidはブール値ではなく、その妥当性を識別するためにenumを作成しました.なぜですか.後で表示されます.
出力-ビューモデルの外側に露出している値.
type -入力と出力のラッパー.これはパスの感覚をもたらし、ビューモデルからアクセシビリティを制御するのに役立つ. 次に、ビューモデル実装
ではinit ()の結合を破壊しましょう
チェック入力有効性
これはメールとパスワードの入力をチェックし、有効であればチェックし、isemailとisspwordwordable にバインドします
有効でない場合にエラーメッセージを返す
現在、IsEmailValidとIssuasswordValidがトリガされているので、それぞれの値が値を持ち、有効でない場合はエラーメッセージを返します.
空のエラーメッセージが有効な場合
現在有効なエラーメッセージを空にします. それでは、ビューコントローラに適用しましょう.
最初に、このビューコントローラのビューモデルとサブビューを初期化しました. 現在、私が サブビューの設定をスキップし、
テキストフィールドからViewModel入力関数への結合
入力された電子メールまたはパスワードの妥当性に基づくテキストフィールドの境界線の変化
私がBoolを使わなかった理由を覚えて、代わりにenumを使いました?このため、ボーダーカラーをTextFieldの妥当性の状態に付けたかったのです.どうやってやったのか
私は
現在、境界線をenumからtextfieldの境界色に直接バインドすることができます. 次に、入力データが有効でない場合、ViewModelからエラーを表示します.
この種のアーキテクチャは突然変異とアクセス可能な変数の分離に役立った.パート2は、この種のアプローチでユニットテストを行うのがいかに楽になるかについてです.ところで、このプロジェクトはrepositoryです.
以下は私が今使っているもののサンプルです.私は、ユーザーからの入力データの検証に焦点を当てフォームアプリケーションで簡単な記号を作成しました.
ところで、私はRXSWIFTを反応部分とSnapKitのために使用しています.始めましょう!
// Enum for validity check
enum TextFieldStatus {
case valid, notValid
}
import RxCocoa
import RxSwift
protocol SigninViewModelInputs {
func didChange(email: String)
func didChange(password: String)
}
protocol SigninViewModelOutputs {
var isEmailValid: PublishRelay<TextFieldStatus> { get }
var isPasswordValid: PublishRelay<TextFieldStatus> { get }
var emailNotValidErr: PublishRelay<String> { get }
var passwordNotValidErr: PublishRelay<String> { get }
}
protocol SigninViewModelTypes {
var inputs: SigninViewModelInputs { get }
var outputs: SigninViewModelOutputs { get }
}
ブレイクダウン
ご覧のように、3つのプロトコルがあります.
入力-主にビューコントローラからの操作またはどこにこれを必要とします.
ご覧のように、IsEmailValidとIssuasswordValidはブール値ではなく、その妥当性を識別するためにenumを作成しました.なぜですか.後で表示されます.
出力-ビューモデルの外側に露出している値.
type -入力と出力のラッパー.これはパスの感覚をもたらし、ビューモデルからアクセシビリティを制御するのに役立つ.
signinviewmodel。スウィフト
class SigninViewModel: SigninViewModelTypes, SigninViewModelOutputs, SigninViewModelInputs {
var inputs: SigninViewModelInputs { return self }
var outputs: SigninViewModelOutputs { return self }
var isEmailValid: PublishRelay<TextFieldStatus> = PublishRelay()
var isPasswordValid: PublishRelay<TextFieldStatus> = PublishRelay()
var emailNotValidErr: PublishRelay<String> = PublishRelay()
var passwordNotValidErr: PublishRelay<String> = PublishRelay()
private var disposeBag: DisposeBag = DisposeBag()
private var didChangeEmailProperty = PublishSubject<String>()
func didChange(email: String) {
didChangeEmailProperty.onNext(email)
}
private var didChangePasswordProperty = PublishSubject<String>()
func didChange(password: String) {
didChangePasswordProperty.onNext(password)
}
init() {
didChangeEmailProperty.map(isValidEmail(_:)).bind(to: isEmailValid).disposed(by: disposeBag)
isEmailValid.filter { $0 == .notValid }
.map { _ in "Entered email is not valid." }
.bind(to: emailNotValidErr)
.disposed(by: disposeBag)
didChangePasswordProperty
.map { $0.count > 5 && $0.count < 21 ? .valid : .notValid }
.bind(to: isPasswordValid)
.disposed(by: disposeBag)
isPasswordValid.filter { $0 == .notValid }
.map { _ in "Password has to be from 6 to 20 characters long." }
.bind(to: passwordNotValidErr)
.disposed(by: disposeBag)
isEmailValid.filter { $0 == .valid }
.map { _ in "" }
.bind(to: emailNotValidErr)
.disposed(by: disposeBag)
isPasswordValid.filter { $0 == .valid }
.map { _ in "" }
.bind(to: passwordNotValidErr)
.disposed(by: disposeBag)
}
private func isValidEmail(_ email: String) -> TextFieldStatus {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailPred.evaluate(with: email) ? .valid : .notValid
}
}
ブレイクダウン
private var didChangeEmailProperty = PublishSubject<String>()
func didChange(email: String) {
didChangeEmailProperty.onNext(email)
}
private var didChangePasswordProperty = PublishSubject<String>()
func didChange(password: String) {
didChangePasswordProperty.onNext(password)
}
ご覧のように、入力関数あたりの内部プロパティを作成したので、init()
で直接検証することはできません.ではinit ()の結合を破壊しましょう
チェック入力有効性
didChangeEmailProperty.map(isValidEmail(_:)).bind(to: isEmailValid).disposed(by: disposeBag)
didChangePasswordProperty
.map { $0.count > 5 && $0.count < 21 ? .valid : .notValid }
.bind(to: isPasswordValid)
.disposed(by: disposeBag)
有効でない場合にエラーメッセージを返す
isEmailValid.filter { $0 == .notValid }
.map { _ in "Entered email is not valid." }
.bind(to: emailNotValidErr)
.disposed(by: disposeBag)
isPasswordValid.filter { $0 == .notValid }
.map { _ in "Password has to be from 6 to 20 characters long." }
.bind(to: passwordNotValidErrMssg)
.disposed(by: disposeBag)
空のエラーメッセージが有効な場合
isEmailValid.filter { $0 == .valid }
.map { _ in "" }
.bind(to: emailNotValidErrMssg)
.disposed(by: disposeBag)
isPasswordValid.filter { $0 == .valid }
.map { _ in "" }
.bind(to: passwordNotValidErrMssg)
.disposed(by: disposeBag)
signinviewcontroller。スウィフト
class SigninViewController: UIViewController {
var viewModel: SigninViewModelTypes
lazy var emailTextField: UITextField = UITextField()
lazy var emailErrLabel: UILabel = UILabel()
lazy var passwordTextField: UITextField = UITextField()
lazy var passwordErrLabel: UILabel = UILabel()
lazy var signinButton: UIButton = UIButton()
lazy var disposeBag = DisposeBag()
init(viewModel: SigninViewModelTypes) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
view.backgroundColor = .white
setupScene()
}
override func viewDidLoad() {
super.viewDidLoad()
setupBindings()
}
private func setupBindings() {
emailTextField.rx.text.orEmpty.distinctUntilChanged()
.bind(onNext: viewModel.inputs.didChange(email:))
.disposed(by: disposeBag)
passwordTextField.rx.text.orEmpty.distinctUntilChanged()
.bind(onNext: viewModel.inputs.didChange(password:))
.disposed(by: disposeBag)
viewModel.outputs.isEmailValid.map { $0.borderColor }
.bind(to: self.emailTextField.rx.borderColor)
.disposed(by: disposeBag)
viewModel.outputs.isPasswordValid.map { $0.borderColor }
.bind(to: self.passwordTextField.rx.borderColor)
.disposed(by: disposeBag)
viewModel.outputs.emailNotValidErrMssg.bind(to: emailErrLabel.rx.text).disposed(by: disposeBag)
viewModel.outputs.passwordNotValidErrMssg.bind(to: passwordErrLabel.rx.text).disposed(by: disposeBag)
viewModel.outputs.emailNotValidErrMssg
.map { $0.isEmpty }
.bind(to: emailErrLabel.rx.isHidden)
.disposed(by: disposeBag)
viewModel.outputs.passwordNotValidErrMssg
.map { $0.isEmpty }
.bind(to: passwordErrLabel.rx.isHidden)
.disposed(by: disposeBag)
}
}
今、それを壊しましょう.SigninViewModel
を私の変数viewModel
のデータ型として入れなかったことに気づいたなら、代わりに、私はSigninViewModelTypes
を使用しました.私がSigninViewModel
を使用したならば、私は私が使用したいinputs
とoutputs
議定書を迂回するクラスの中で直接変数にアクセスすることができます.viewModel.inputs.someFunction()
の内部にある結合にフォーカスしましょう.テキストフィールドからViewModel入力関数への結合
emailTextField.rx.text.orEmpty.distinctUntilChanged()
.bind(onNext: viewModel.inputs.didChange(email:))
.disposed(by: disposeBag)
passwordTextField.rx.text.orEmpty.distinctUntilChanged()
.bind(onNext: viewModel.inputs.didChange(password:))
.disposed(by: disposeBag)
入力された電子メールまたはパスワードの妥当性に基づくテキストフィールドの境界線の変化
viewModel.outputs.isEmailValid.map { $0.borderColor }
.bind(to: emailTextField.rx.borderColor)
.disposed(by: disposeBag)
viewModel.outputs.isPasswordValid.map { $0.borderColor }
.bind(to: passwordTextField.rx.borderColor)
.disposed(by: disposeBag)
enum TextFieldStatus {
case valid, notValid
var borderColor: CGColor {
switch self {
case .valid: return UIColor.lightGray.cgColor
default: return UIColor.red.cgColor
}
}
}
viewModel.someFunction()
という変数を追加し、ケースに基づいてCGColorを定義しました.そういうわけで、我々は例としてIssasswordValidをCGColorに写像することができて、それをtextfieldの境界色に縛ることができます、しかし、あなたが私がそれを知っているかどうか疑問に思っているならば、待ってください、そして、setupBindings()
がRXSwiftでborderColor
として利用できないということを知っていてください.よく、私は拡張をつくりました、そして、それのためのコードはここにあります.extension Reactive where Base: UITextField {
public var borderColor: Binder<CGColor> {
return Binder(base, binding: { textField, active in
textField.layer.borderColor = active
})
}
}
viewModel.outputs.emailNotValidErrMssg.bind(to: emailErrLabel.rx.text).disposed(by: disposeBag)
viewModel.outputs.passwordNotValidErrMssg.bind(to: passwordErrLabel.rx.text).disposed(by: disposeBag)
では、テキストフィールドの検証を行います.この種のアーキテクチャは突然変異とアクセス可能な変数の分離に役立った.パート2は、この種のアプローチでユニットテストを行うのがいかに楽になるかについてです.ところで、このプロジェクトはrepositoryです.
Reference
この問題について(リアクティブプログラミングを用いたビューモデルのI/Oアプローチ), 我々は、より多くの情報をここで見つけました https://dev.to/jaimejazarenoiii/view-model-s-i-o-approach-applying-reactive-programming-1flテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol