반응식 프로그래밍을 응용한 보기 모델 입출력 방법
41136 단어 mvvmarchitecturerxswiftswift
다음은 내가 현재 사용하고 있는 예이다.나는 사용자가 입력한 데이터를 검증하는 데 중점을 두는 간단한 로그인 폼 프로그램을 만들었다.
그나저나 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 }
}
세분화:
보시다시피 저는 세 가지 협의가 있습니다.
입력 - 주로 보기 컨트롤러나 필요한 조작에서 나온다.
보시다시피 isEmailValid와 isPasswordValid는 부울 값이 아니지만, 유효성을 표시하기 위해 매거진을 만들었습니다.왜?이따가 보게 될 거야.
내보내기 - 뷰 모델 외부에 표시되는 값입니다.
유형 - 입력과 출력의 포장기.이것은 경로 감각을 가져오고 보기 모델에서 접근성을 제어하는 데 도움이 되며, 잠시 후에 우리가 왜 그것을 필요로 하는지 보실 수 있습니다.
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()
에서 그것을 직접 검증하는 것이 아니라 SigninViewModel
에서 그것을 관찰할 수 있습니다.이제 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)
컨트롤러에 로그인합니다.날래다
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)
}
}
이제 분석해 봅시다.viewModel
을 변수 SigninViewModelTypes
의 데이터 형식으로 하지 않고 SigninViewModel
을 사용한 것을 알아차린다면 왜?만약에 내가 inputs
을 사용한다면 나는 클래스의 변수를 직접 방문할 수 있다. 이것은 내가 사용하고 싶은 outputs
과 viewModel.inputs.someFunction()
프로토콜을 돌아갈 것이다. 그래서 나는 의외로 viewModel.someFunction()
을 사용할 수 있다. 이것은 내가 피하고 싶은 것이다.setupBindings()
의 귀속에 중점을 두겠습니다. 이제 분해하겠습니다.viewModel 입력 함수에 from textField 바인딩
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)
입력한 전자 메일 또는 암호의 유효성에 따라 textField 테두리 색상 변경
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
}
}
}
borderColor
이라는 변수를 추가했고 사례에 따라 cgColor를 정의했습니다.이것이 바로 우리가 isPasswordValid를 cgColor에 비추어 예로 삼아 텍스트 필드의borderColor에 연결할 수 있는 이유입니다. 그러나 제가 어떻게 했는지 알고 싶다면 borderColor
은 RxSwift에서 Binder
으로 제공할 수 없다는 것을 알고 있습니다.확장자를 만들었습니다. 다음은 코드입니다.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)
이제 텍스트 필드에 대한 검증이 완료되었습니다. 다음은 텍스트 필드의 모양입니다.이런 구조는 내가 돌연변이와 접근 가능한 변수를 분리하는 데 도움을 준다.두 번째 부분은 제작 중입니다. 이런 방법으로 단원 테스트를 하는 것이 얼마나 쉬운지 토론하겠습니다.참고로 이것은 이 프로젝트의 repository입니다.
Reference
이 문제에 관하여(반응식 프로그래밍을 응용한 보기 모델 입출력 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/jaimejazarenoiii/view-model-s-i-o-approach-applying-reactive-programming-1fl텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)