ios - 在 iOS RxSwift 中用适当的值区分 errorObserver
问题描述
我有一个 LoginViewModel 和 LoginViewController 类。在 LoginViewController 中有 2 个文本字段用户名、密码和一个登录按钮,当用户单击登录按钮时,在 LoginViewModel 类中验证用户名和密码字段,如果它为空,则将相应的消息传递给 LoginViewController 类并在相应的文本字段中显示消息.
class LoginViewModel : LoginViewModelProtocol {
var errorObservable: PublishSubject<String> = PublishSubject<String>()
var userName: BehaviorRelay<String> = BehaviorRelay(value: "")
var password: BehaviorRelay<String> = BehaviorRelay(value: "")
let disposeBag = DisposeBag()
var apiClient : ApiClientProtocol
public init(fetcher : ApiClientProtocol) {
apiClient = fetcher
}
func validateUserName(_ value: String) -> Bool {
if value.count == 0 {
errorObservable.onNext("This field is required")
return false
}
return true
}
func validatePassword(_ value: String) -> Bool {
if value.count == 0 {
errorObservable.onNext("This field is required")
return false
}
return true
}
func onLoginButtonClick(){
if self.validateUserName(userName.value) && self.validatePassword(password.value) {
// apiClient.performLogin(userName: userName.value, password: password.value)
}
}
}
//validateUserName 和 validatePassword 应该分别通知用户名和密码字段。
class LoginViewController: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var loginButton: UIButton!
let disposeBag = DisposeBag()
var viewModel: LoginViewModelProtocol!
override func viewDidLoad() {
super.viewDidLoad()
initialiseUI()
configureBinding()
}
func configureBinding() {
usernameTextField.rx.text
.orEmpty
.bind(to: viewModel.userName)
.disposed(by: disposeBag)
passwordTextField.rx.text
.orEmpty
.bind(to: viewModel.password)
.disposed(by: disposeBag)
viewModel.errorObservable.asObserver().subscribe(onNext: { (error) in
self.updateUI(error)
}, onDisposed: {})
}
func updateUI(_ error : String){
let fontS = UIFont.systemFont(ofSize: 12)
let attributes = [
NSAttributedString.Key.foregroundColor: UIColor.red,
NSAttributedString.Key.font : fontS
] as [NSAttributedString.Key : Any]
self.passwordTextField.attributedPlaceholder = NSAttributedString(string: error, attributes: attributes)
}
}
当两个文本字段都为空时,我想在两个文本字段中显示错误消息 当 RxSwift 为空时,如何使用 errorObserver 为 textField 和密码传递值?
解决方案
这里的关键是产生两个不同的错误输出。像下面这样的东西会起作用,虽然我不喜欢有这么多学科......
final class LoginViewController: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var loginButton: UIButton!
var viewModel: LoginViewModel!
override func viewDidLoad() {
super.viewDidLoad()
let inputs = LoginInputs(
userName: usernameTextField.rx.text.orEmpty.asObservable(),
password: passwordTextField.rx.text.orEmpty.asObservable(),
trigger: loginButton.rx.tap.asObservable()
)
viewModel.attach(inputs)
disposeBag.insert(
bind(input: viewModel.userNameError, output: usernameTextField.rx.attributedText),
bind(input: viewModel.passwordError, output: passwordTextField.rx.attributedText),
viewModel.loginSuccess.bind(onNext: { print("login success:", $0) })
)
}
func bind(input: Observable<String>, output: ControlProperty<NSAttributedString?>) -> Disposable {
let attributes = [
NSAttributedString.Key.foregroundColor: UIColor.red,
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 12)
] as [NSAttributedString.Key : Any]
return input
.map { NSAttributedString(string: $0, attributes: attributes) }
.bind(to: output)
}
private let disposeBag = DisposeBag()
}
struct LoginInputs {
let userName: Observable<String>
let password: Observable<String>
let trigger: Observable<Void>
}
struct LoginViewModel {
let userNameError: Observable<String>
let passwordError: Observable<String>
let loginSuccess: Observable<Bool>
init(fetcher: ApiClientProtocol) {
self.fetcher = fetcher
userNameError = _userNameError.asObservable()
passwordError = _passwordError.asObservable()
loginSuccess = _loginSuccess.asObservable()
}
func attach(_ inputs: LoginInputs) {
disposeBag.insert(
bind(trigger: inputs.trigger, input: inputs.userName, output: _userNameError.asObserver()),
bind(trigger: inputs.trigger, input: inputs.password, output: _passwordError.asObserver())
)
let credentials = Observable.combineLatest(inputs.userName, inputs.password) { (username: $0, password: $1) }
inputs.trigger
.withLatestFrom(credentials)
.filter { !$0.username.isEmpty && !$0.password.isEmpty}
.flatMapLatest { [fetcher] in
fetcher.performLogin(userName: $0.username, password: $0.password)
.map { true }
.catchErrorJustReturn(false)
}
.bind(to: _loginSuccess)
.disposed(by: disposeBag)
}
private func bind(trigger: Observable<Void>, input: Observable<String>, output: AnyObserver<String>) -> Disposable {
return trigger
.withLatestFrom(input)
.filter { $0.isEmpty }
.map { _ in "This field is required" }
.bind(to: output)
}
private let fetcher: ApiClientProtocol
private let _userNameError = PublishSubject<String>()
private let _passwordError = PublishSubject<String>()
private let _loginSuccess = PublishSubject<Bool>()
private let disposeBag = DisposeBag()
}
protocol ApiClientProtocol {
func performLogin(userName: String, password: String) -> Observable<Void>
}
推荐阅读
- visual-studio - Access 数据库中未显示更改
- c# - HTTP 错误 404.0 - 未找到 - MVC 映射路由
- c++ - 实时 GPU 路径跟踪,使用 OIDN 去噪(Intel Open Image Denoise)
- sqlite - Sqlite 合并两个相同模式的数据库,不包括重复项
- http - 404 函数未找到 vs 404 数据未找到
- php - 任何人都知道如何在php中使用数组复选框元素进行表单输入?
- c - 为什么我没有输出?
- vue.js - Vue:src 挂载钩子中的错误:“TypeError:无法读取未定义的属性'0'”
- mysql - 尝试连接到 phpmyadmin mysql 数据库时,用户 ''@'localhost' 的访问被拒绝(使用密码:否)
- knex.js - 将两个列与查询生成器 adonis.js 进行比较