Swift - RxSwift的使用详解55(一个用户注册样例2:显示网络请求活动指示器)
当我们发起网络请求,或者进行其他一些比较耗时的操作时,最好给用户一个指示。比如上文的用户注册样例,当点击注册按钮后会等待 1.5 秒才返回结果,那么为了更好的用户体验这时就可以显示个活动指示器。
下面我通过样例演示几种不同的活动指示器用法,以及他们如何根据请求操作自动进行隐藏和显示。
一、准备工作
1,引入 ActivityIndicator
(1)ActivityIndicator 类可不是苹果自带的 UIActivityIndicator,它是一个用来监测是否有序列正在发送元素的类:
- 如果至少还有一个序列正在工作,那么它会返回一个 true。
- 如果没有序列在工作了,那么它会返回一个 false 值。
(2)默认情况下项目引入的 RxSwift 和 RxCocoa 库中是不会有个类的,我们需要手动将 RxSwift 源码包中的 RxExample/Services/ActivityIndicator.swift 这个文件添加到我们项目中来。
2,修改 ViewModel
(1)接着对前文的 GitHubSignupViewModel 做个修改,增加了个 signingIn 序列,用于表示当前是否正在“发请求注册中”。
(2)我们在请求序列中使用 trackActivity 方法可以把这个请求序列放入制定的 activityIndicator 中进行监测,监测结果则做为 signingIn 序列。
import RxSwift import RxCocoa class GitHubSignupViewModel { //用户名验证结果 let validatedUsername: Driver<ValidationResult> //密码验证结果 let validatedPassword: Driver<ValidationResult> //再次输入密码验证结果 let validatedPasswordRepeated: Driver<ValidationResult> //注册按钮是否可用 let signupEnabled: Driver<Bool> //正在注册中 let signingIn: Driver<Bool> //注册结果 let signupResult: Driver<Bool> //ViewModel初始化(根据输入实现对应的输出) init( input: ( username: Driver<String>, password: Driver<String>, repeatedPassword: Driver<String>, loginTaps: Signal<Void> ), dependency: ( networkService: GitHubNetworkService, signupService: GitHubSignupService )) { //用户名验证 validatedUsername = input.username .flatMapLatest { username in return dependency.signupService.validateUsername(username) .asDriver(onErrorJustReturn: .failed(message: "服务器发生错误!")) } //用户名密码验证 validatedPassword = input.password .map { password in return dependency.signupService.validatePassword(password) } //重复输入密码验证 validatedPasswordRepeated = Driver.combineLatest( input.password, input.repeatedPassword, resultSelector: dependency.signupService.validateRepeatedPassword) //注册按钮是否可用 signupEnabled = Driver.combineLatest( validatedUsername, validatedPassword, validatedPasswordRepeated ) { username, password, repeatPassword in username.isValid && password.isValid && repeatPassword.isValid } .distinctUntilChanged() //获取最新的用户名和密码 let usernameAndPassword = Driver.combineLatest(input.username, input.password) { (username: $0, password: $1) } //用于检测是否正在请求数据 let activityIndicator = ActivityIndicator() self.signingIn = activityIndicator.asDriver() //注册按钮点击结果 signupResult = input.loginTaps.withLatestFrom(usernameAndPassword) .flatMapLatest { pair in //也可考虑改用flatMapFirst return dependency.networkService.signup(pair.username, password: pair.password) .trackActivity(activityIndicator) //把当前序列放入signing序列中进行检测 .asDriver(onErrorJustReturn: false) } } }
二、顶部状态栏联网指示器的绑定
1,效果图
(1)当我们点击注册按钮发起请求时,顶部状态栏会显示菊花状的网络请求指示器。
(2)当注册结果返回时,顶部的网络请求指示器消失。
2,样例代码
这个只需要在主视图控制器中将 signingIn 绑定到 UIApplication 的 isNetworkActivityIndicatorVisible 属性上即可。
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { //用户名输入框、以及验证结果显示标签 @IBOutlet weak var usernameOutlet: UITextField! @IBOutlet weak var usernameValidationOutlet: UILabel! //密码输入框、以及验证结果显示标签 @IBOutlet weak var passwordOutlet: UITextField! @IBOutlet weak var passwordValidationOutlet: UILabel! //重复密码输入框、以及验证结果显示标签 @IBOutlet weak var repeatedPasswordOutlet: UITextField! @IBOutlet weak var repeatedPasswordValidationOutlet: UILabel! //注册按钮 @IBOutlet weak var signupOutlet: UIButton! let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //初始化ViewModel let viewModel = GitHubSignupViewModel( input: ( username: usernameOutlet.rx.text.orEmpty.asDriver(), password: passwordOutlet.rx.text.orEmpty.asDriver(), repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asDriver(), loginTaps: signupOutlet.rx.tap.asSignal() ), dependency: ( networkService: GitHubNetworkService(), signupService: GitHubSignupService() ) ) //用户名验证结果绑定 viewModel.validatedUsername .drive(usernameValidationOutlet.rx.validationResult) .disposed(by: disposeBag) //密码验证结果绑定 viewModel.validatedPassword .drive(passwordValidationOutlet.rx.validationResult) .disposed(by: disposeBag) //再次输入密码验证结果绑定 viewModel.validatedPasswordRepeated .drive(repeatedPasswordValidationOutlet.rx.validationResult) .disposed(by: disposeBag) //注册按钮是否可用 viewModel.signupEnabled .drive(onNext: { [weak self] valid in self?.signupOutlet.isEnabled = valid self?.signupOutlet.alpha = valid ? 1.0 : 0.3 }) .disposed(by: disposeBag) //当前是否正在注册 viewModel.signingIn .drive(UIApplication.shared.rx.isNetworkActivityIndicatorVisible) .disposed(by: disposeBag) //注册结果绑定 viewModel.signupResult .drive(onNext: { [unowned self] result in self.showMessage("注册" + (result ? "成功" : "失败") + "!") }) .disposed(by: disposeBag) } //详细提示框 func showMessage(_ message: String) { let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) let okAction = UIAlertAction(title: "确定", style: .cancel, handler: nil) alertController.addAction(okAction) self.present(alertController, animated: true, completion: nil) } }
三、UIActivityIndicatorView 的绑定
1,效果图
(1)当我们点击注册按钮发起请求时,按钮左侧会显示一个菊花状的网络请求指示器。
(2)当注册结果返回时,按钮左侧的网络请求指示器消失。
2,样例代码
(1)首先打开 StoryBoard,在注册按钮的左侧放置一个 Activity Indicator View,同时设置当其动画停止时自动隐藏,并将其与代码做 @IBOutlet 绑定。
(2)最后只需要在主视图控制器中将 signingIn 绑定到 Activity Indicator View 的 isAnimating 属性上即可。
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { //用户名输入框、以及验证结果显示标签 @IBOutlet weak var usernameOutlet: UITextField! @IBOutlet weak var usernameValidationOutlet: UILabel! //密码输入框、以及验证结果显示标签 @IBOutlet weak var passwordOutlet: UITextField! @IBOutlet weak var passwordValidationOutlet: UILabel! //重复密码输入框、以及验证结果显示标签 @IBOutlet weak var repeatedPasswordOutlet: UITextField! @IBOutlet weak var repeatedPasswordValidationOutlet: UILabel! //注册按钮 @IBOutlet weak var signupOutlet: UIButton! //注册时的活动指示器 @IBOutlet weak var signInActivityIndicator: UIActivityIndicatorView! let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //初始化ViewModel let viewModel = GitHubSignupViewModel( input: ( username: usernameOutlet.rx.text.orEmpty.asDriver(), password: passwordOutlet.rx.text.orEmpty.asDriver(), repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asDriver(), loginTaps: signupOutlet.rx.tap.asSignal() ), dependency: ( networkService: GitHubNetworkService(), signupService: GitHubSignupService() ) ) //用户名验证结果绑定 viewModel.validatedUsername .drive(usernameValidationOutlet.rx.validationResult) .disposed(by: disposeBag) //密码验证结果绑定 viewModel.validatedPassword .drive(passwordValidationOutlet.rx.validationResult) .disposed(by: disposeBag) //再次输入密码验证结果绑定 viewModel.validatedPasswordRepeated .drive(repeatedPasswordValidationOutlet.rx.validationResult) .disposed(by: disposeBag) //注册按钮是否可用 viewModel.signupEnabled .drive(onNext: { [weak self] valid in self?.signupOutlet.isEnabled = valid self?.signupOutlet.alpha = valid ? 1.0 : 0.3 }) .disposed(by: disposeBag) //当前是否正在注册 viewModel.signingIn .drive(signInActivityIndicator.rx.isAnimating) .disposed(by: disposeBag) //注册结果绑定 viewModel.signupResult .drive(onNext: { [unowned self] result in self.showMessage("注册" + (result ? "成功" : "失败") + "!") }) .disposed(by: disposeBag) } //详细提示框 func showMessage(_ message: String) { let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) let okAction = UIAlertAction(title: "确定", style: .cancel, handler: nil) alertController.addAction(okAction) self.present(alertController, animated: true, completion: nil) } }
四、第三方指示器的绑定
这里我以 MBProgressHUD 这个第三方透明指示器为例做演示,关于 MBProgressHUD 相关介绍和配置方法,可以参考我之前写的这篇文章:
1,效果图
(1)当我们点击注册按钮发起请求时,页面中央会显示一个菊花状的网络请求指示器。
(2)当注册结果返回时,页面中央的网络请求指示器消失。
2,样例代码
我们同样地将 signingIn 绑定到指示器地显示隐藏属性上即可。
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { //用户名输入框、以及验证结果显示标签 @IBOutlet weak var usernameOutlet: UITextField! @IBOutlet weak var usernameValidationOutlet: UILabel! //密码输入框、以及验证结果显示标签 @IBOutlet weak var passwordOutlet: UITextField! @IBOutlet weak var passwordValidationOutlet: UILabel! //重复密码输入框、以及验证结果显示标签 @IBOutlet weak var repeatedPasswordOutlet: UITextField! @IBOutlet weak var repeatedPasswordValidationOutlet: UILabel! //注册按钮 @IBOutlet weak var signupOutlet: UIButton! let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //初始化ViewModel let viewModel = GitHubSignupViewModel( input: ( username: usernameOutlet.rx.text.orEmpty.asDriver(), password: passwordOutlet.rx.text.orEmpty.asDriver(), repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asDriver(), loginTaps: signupOutlet.rx.tap.asSignal() ), dependency: ( networkService: GitHubNetworkService(), signupService: GitHubSignupService() ) ) //用户名验证结果绑定 viewModel.validatedUsername .drive(usernameValidationOutlet.rx.validationResult) .disposed(by: disposeBag) //密码验证结果绑定 viewModel.validatedPassword .drive(passwordValidationOutlet.rx.validationResult) .disposed(by: disposeBag) //再次输入密码验证结果绑定 viewModel.validatedPasswordRepeated .drive(repeatedPasswordValidationOutlet.rx.validationResult) .disposed(by: disposeBag) //注册按钮是否可用 viewModel.signupEnabled .drive(onNext: { [weak self] valid in self?.signupOutlet.isEnabled = valid self?.signupOutlet.alpha = valid ? 1.0 : 0.3 }) .disposed(by: disposeBag) //创建一个指示器 let hud = MBProgressHUD.showAdded(to: self.view, animated: true) //当前是否正在注册,决定指示器是否显示 viewModel.signingIn .map{ !$0 } .drive(hud.rx.isHidden) .disposed(by: disposeBag) //注册结果绑定 viewModel.signupResult .drive(onNext: { [unowned self] result in self.showMessage("注册" + (result ? "成功" : "失败") + "!") }) .disposed(by: disposeBag) } //详细提示框 func showMessage(_ message: String) { let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) let okAction = UIAlertAction(title: "确定", style: .cancel, handler: nil) alertController.addAction(okAction) self.present(alertController, animated: true, completion: nil) } }