? ? ? ? 當我們發起網絡請求,或者進行其他一些比較耗時的操作時,最好給用戶一個指示。比如上文的用戶注冊樣例,當點擊注冊按鈕后會等待 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)
}
}