你好 RxSwift!

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

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

  • 当用户输入用户名时,如果用户名不足 5 个字就给出红色提示语,并且无法输入密码,当用户名符合要求时才可以输入密码。
  • 同样的当用户输入的密码不到 5 个字时也给出红色提示语。
  • 当用户名和密码有一个不符合要求时底部的绿色按钮不可点击,只有当用户名和密码同时有效时按钮才可点击。
  • 当点击绿色按钮后弹出一个提示框,这个提示框只是用来做演示而已。

你可以下载这个例子并在模拟器上运行,这样可以帮助于你理解整个程序的交互:

2. 你好 RxSwift! - 图1

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

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

这里需要完成 4 个交互:

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

    2. 你好 RxSwift! - 图2

    1. override func viewDidLoad() {
    2. super.viewDidLoad()
    3. ...
    4. // 用户名是否有效
    5. let usernameValid = usernameOutlet.rx.text.orEmpty
    6. // 用户名 -> 用户名是否有效
    7. .map { $0.count >= minimalUsernameLength }
    8. .share(replay: 1)
    9. ...
    10. // 用户名是否有效 -> 密码输入框是否可用
    11. usernameValid
    12. .bind(to: passwordOutlet.rx.isEnabled)
    13. .disposed(by: disposeBag)
    14. // 用户名是否有效 -> 用户名提示语是否隐藏
    15. usernameValid
    16. .bind(to: usernameValidOutlet.rx.isHidden)
    17. .disposed(by: disposeBag)
    18. ...
    19. }

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

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

    2. 你好 RxSwift! - 图3

    1. override func viewDidLoad() {
    2. super.viewDidLoad()
    3. ...
    4. // 密码是否有效
    5. let passwordValid = passwordOutlet.rx.text.orEmpty
    6. // 密码 -> 密码是否有效
    7. .map { $0.count >= minimalPasswordLength }
    8. .share(replay: 1)
    9. ...
    10. // 密码是否有效 -> 密码提示语是否隐藏
    11. passwordValid
    12. .bind(to: passwordValidOutlet.rx.isHidden)
    13. .disposed(by: disposeBag)
    14. ...
    15. }

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

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

    2. 你好 RxSwift! - 图4

    1. override func viewDidLoad() {
    2. super.viewDidLoad()
    3. ...
    4. // 用户名是否有效
    5. let usernameValid = ...
    6. // 密码是否有效
    7. let passwordValid = ...
    8. ...
    9. // 所有输入是否有效
    10. let everythingValid = Observable.combineLatest(
    11. usernameValid,
    12. passwordValid
    13. ) { $0 && $1 } // 取用户名和密码同时有效
    14. .share(replay: 1)
    15. ...
    16. // 所有输入是否有效 -> 绿色按钮是否可点击
    17. everythingValid
    18. .bind(to: doSomethingOutlet.rx.isEnabled)
    19. .disposed(by: disposeBag)
    20. ...
    21. }

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

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

    2. 你好 RxSwift! - 图5

    1. override func viewDidLoad() {
    2. super.viewDidLoad()
    3. ...
    4. // 点击绿色按钮 -> 弹出提示框
    5. doSomethingOutlet.rx.tap
    6. .subscribe(onNext: { [weak self] in self?.showAlert() })
    7. .disposed(by: disposeBag)
    8. }
    9. func showAlert() {
    10. let alertView = UIAlertView(
    11. title: "RxExample",
    12. message: "This is wonderful",
    13. delegate: nil,
    14. cancelButtonTitle: "OK"
    15. )
    16. alertView.show()
    17. }

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

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

2. 你好 RxSwift! - 图6

然后看一下完整的代码:

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. usernameValidOutlet.text = "Username has to be at least \(minimalUsernameLength) characters"
  4. passwordValidOutlet.text = "Password has to be at least \(minimalPasswordLength) characters"
  5. let usernameValid = usernameOutlet.rx.text.orEmpty
  6. .map { $0.count >= minimalUsernameLength }
  7. .share(replay: 1)
  8. let passwordValid = passwordOutlet.rx.text.orEmpty
  9. .map { $0.count >= minimalPasswordLength }
  10. .share(replay: 1)
  11. let everythingValid = Observable.combineLatest(
  12. usernameValid,
  13. passwordValid
  14. ) { $0 && $1 }
  15. .share(replay: 1)
  16. usernameValid
  17. .bind(to: passwordOutlet.rx.isEnabled)
  18. .disposed(by: disposeBag)
  19. usernameValid
  20. .bind(to: usernameValidOutlet.rx.isHidden)
  21. .disposed(by: disposeBag)
  22. passwordValid
  23. .bind(to: passwordValidOutlet.rx.isHidden)
  24. .disposed(by: disposeBag)
  25. everythingValid
  26. .bind(to: doSomethingOutlet.rx.isEnabled)
  27. .disposed(by: disposeBag)
  28. doSomethingOutlet.rx.tap
  29. .subscribe(onNext: { [weak self] in self?.showAlert() })
  30. .disposed(by: disposeBag)
  31. }
  32. func showAlert() {
  33. let alertView = UIAlertView(
  34. title: "RxExample",
  35. message: "This is wonderful",
  36. delegate: nil,
  37. cancelButtonTitle: "OK"
  38. )
  39. alertView.show()
  40. }

你会发现你可以用几行代码完成如此复杂的交互。这可以大大提升我们的开发效率。

更多疑问

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

    我们用 usernameValid 来控制用户名提示语是否隐藏以及密码输入框是否可用。shareReplay 就是让他们共享这一个源,而不是为他们单独创建新的源。这样可以减少不必要的开支。

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

    和我们所熟悉的对象一样,每一个绑定也是有生命周期的。并且这个绑定是可以被清除的。disposed(by: disposeBag)就是将绑定的生命周期交给 disposeBag 来管理。当 disposeBag 被释放的时候,那么里面尚未清除的绑定也就被清除了。这就相当于是在用 ARC 来管理绑定的生命周期。 这个内容会在 Disposable 章节详细介绍。