首页 > 解决方案 > 在 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 和密码传递值?

标签: iosswiftobservablerx-swiftrx-cocoa

解决方案


这里的关键是产生两个不同的错误输出。像下面这样的东西会起作用,虽然我不喜欢有这么多学科......

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>
}

推荐阅读