当前位置: > > > Swift - RxSwift的使用详解55(一个用户注册样例2:显示网络请求活动指示器)

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)
    }
}
评论0