你好 RxSwift

我的第一个 RxSwift 应用程序 – 输入验证:

这是一个模仿用户登录的程序。

  • 当用户输入用户名时,假如用户名不足 5 个字就给出赤色提示语,而且无法输入暗码,当用户名符合要求时才能够输入暗码。
  • 同样的当用户输入的暗码不到 5 个字时也给出赤色提示语。
  • 当用户名和暗码有一个不符合要求时底部的绿色按钮不可点击,只有当用户名和暗码一起有用时按钮才可点击。
  • 当点击绿色按钮后弹出一个提示框,这个提示框只是用来做演示罢了。

你能够下载这个比如并在模仿器上运转,这样能够帮助于你了解整个程序的交互:

第五篇 RxSwift登录页Demo

这个页面主要由 5 各元素组成:

  1. 用户名输入框
  2. 用户名提示语(赤色)
  3. 暗码输入框
  4. 暗码提示语(赤色)
  5. 操作按钮(绿色)
class SimpleValidationViewController : ViewController {
    @IBOutlet weak var usernameOutlet: UITextField!
    @IBOutlet weak var usernameValidOutlet: UILabel!
    @IBOutlet weak var passwordOutlet: UITextField!
    @IBOutlet weak var passwordValidOutlet: UILabel!
    @IBOutlet weak var doSomethingOutlet: UIButton!
    ...
}

这儿需要完成 4 个交互:

  • 当用户名输入不到 5 个字时显现提示语,而且无法输入暗码

    第五篇 RxSwift登录页Demo

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        // 用户名是否有用
        let usernameValid = usernameOutlet.rx.text.orEmpty
            // 用户名 -> 用户名是否有用
            .map { $0.count >= minimalUsernameLength }
            .share(replay: 1)
        ...
        // 用户名是否有用 -> 暗码输入框是否可用
        usernameValid
            .bind(to: passwordOutlet.rx.isEnabled)  
            .disposed(by: disposeBag)
        // 用户名是否有用 -> 用户名提示语是否躲藏
        usernameValid
            .bind(to: usernameValidOutlet.rx.isHidden)
            .disposed(by: disposeBag)
        ...
    }
    

    当用户修改用户名输入框的内容时就会产生一个新的用户名, 然后经过map办法将它转化成用户名是否有用, 最终经过bind(to: ...)来决议暗码输入框是否可用以及提示语是否躲藏。

  • 当暗码输入不到 5 个字时显现提示文字

    第五篇 RxSwift登录页Demo

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        // 暗码是否有用
        let passwordValid = passwordOutlet.rx.text.orEmpty
            // 暗码 -> 暗码是否有用
            .map { $0.count >= minimalPasswordLength }
            .share(replay: 1)
        ...
        // 暗码是否有用 -> 暗码提示语是否躲藏
        passwordValid
            .bind(to: passwordValidOutlet.rx.isHidden)
            .disposed(by: disposeBag)
        ...
    }
    

    这个和用用户名来控制提示语的逻辑是相同的。

  • 当用户名和暗码都符合要求时,绿色按钮才可点击

    第五篇 RxSwift登录页Demo

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        // 用户名是否有用
        let usernameValid = ...
        // 暗码是否有用
        let passwordValid = ...
        ...
        // 所有输入是否有用
        let everythingValid = Observable.combineLatest(
              usernameValid,
              passwordValid
            ) { $0 && $1 } // 取用户名和暗码一起有用
            .share(replay: 1)
        ...
        // 所有输入是否有用 -> 绿色按钮是否可点击
        everythingValid
            .bind(to: doSomethingOutlet.rx.isEnabled)
            .disposed(by: disposeBag)
        ...
    }
    

    经过Observable.combineLatest(...) { ... }来将用户名是否有用以及暗码是都有用合并出两者是否一起有用,然后用它来控制绿色按钮是否可点击。

  • 点击绿色按钮后,弹出一个提示框

    第五篇 RxSwift登录页Demo

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        // 点击绿色按钮 -> 弹出提示框
        doSomethingOutlet.rx.tap
            .subscribe(onNext: { [weak self] in self?.showAlert() })
            .disposed(by: disposeBag)
    }
    func showAlert() {
        let alertView = UIAlertView(
            title: "RxExample",
            message: "This is wonderful",
            delegate: nil,
            cancelButtonTitle: "OK"
        )
        alertView.show()
    }
    

    在点击绿色按钮后,弹出一个提示框

这样 4 个交互都完成了,现在咱们纵观大局看下这个程序是一个什么样的结构:

第五篇 RxSwift登录页Demo

然后看一下完好的代码:

override func viewDidLoad() {
    super.viewDidLoad()
    usernameValidOutlet.text = "Username has to be at least (minimalUsernameLength) characters"
    passwordValidOutlet.text = "Password has to be at least (minimalPasswordLength) characters"
    let usernameValid = usernameOutlet.rx.text.orEmpty
        .map { $0.count >= minimalUsernameLength }
        .share(replay: 1)
    let passwordValid = passwordOutlet.rx.text.orEmpty
        .map { $0.count >= minimalPasswordLength }
        .share(replay: 1)
    let everythingValid = Observable.combineLatest(
          usernameValid,
          passwordValid
        ) { $0 && $1 }
        .share(replay: 1)
    usernameValid
        .bind(to: passwordOutlet.rx.isEnabled)
        .disposed(by: disposeBag)
    usernameValid
        .bind(to: usernameValidOutlet.rx.isHidden)
        .disposed(by: disposeBag)
    passwordValid
        .bind(to: passwordValidOutlet.rx.isHidden)
        .disposed(by: disposeBag)
    everythingValid
        .bind(to: doSomethingOutlet.rx.isEnabled)
        .disposed(by: disposeBag)
    doSomethingOutlet.rx.tap
        .subscribe(onNext: { [weak self] in self?.showAlert() })
        .disposed(by: disposeBag)
}
func showAlert() {
    let alertView = UIAlertView(
        title: "RxExample",
        message: "This is wonderful",
        delegate: nil,
        cancelButtonTitle: "OK"
    )
    alertView.show()
}

你会发现你能够用几行代码完成如此杂乱的交互。这能够大大提高咱们的开发效率。

更多疑问

  • share(replay: 1)是用来做什么的?

    咱们用usernameValid来控制用户名提示语是否躲藏以及暗码输入框是否可用。shareReplay便是让他们共享这一个源,而不是为他们独自创建新的源。这样能够减少不必要的开支。

  • disposed(by: disposeBag)是用来做什么的?

    和咱们所了解的对象相同,每一个绑定也是有生命周期的。而且这个绑定是能够被清除的。disposed(by: disposeBag)便是将绑定的生命周期交给disposeBag来办理。当disposeBag被释放的时候,那么里面没有清除的绑定也就被清除了。这就相当于是在用ARC来办理绑定的生命周期。 这个内容会在Disposable章节详细介绍。