当我们发起网络请求,或者进行其他一些比较耗时的操作时,最好给用户一个指示。比如上文的用户注册样例,当点击注册按钮后会等待 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
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)
}
}
网友评论