本文是官方案例GitHubSignup-UsingDriver学习笔记
项目实现功能
这个登录页面实现了下面几个功能:
1.检验用户名是否可用
2.密码是否符合要求
3.确认密码是符合密码一样
4.上面上面三个都符合要求,登录按钮才可以点击
5.当用户正在登录的时候,显示 activityIndicator,提醒用户等待,此时按钮不能被按;当得到登录结果的时候,隐藏 activityIndicator。
6.登录完成显示登录结果
具体实现
GitHubSignupViewController2.swift
#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif
引用部分RX_NO_MODULE这个宏的字面意思应该是没有rx的模块意思,但是没有找到具体实现在哪里,如果谁知道麻烦告知
@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 signingUpOulet: UIActivityIndicatorView!
首先是各个控件的绑定
let viewModel = GithubSignupViewModel2(
input: (
username: usernameOutlet.rx.text.orEmpty.asDriver(),
password: passwordOutlet.rx.text.orEmpty.asDriver(),
repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asDriver(),
loginTaps: signupOutlet.rx.tap.asDriver()
),
dependency: (
API: GitHubDefaultAPI.sharedAPI,
validationService: GitHubDefaultValidationService.sharedValidationService,
wireframe: DefaultWireframe.sharedInstance
)
)
viewModel初始化,rx是RxSwift的域名,text是观察的属性,orEmpty是检验text是否为nil如果为nil返回"",asDriver()是具体特殊属性的Observable,如果使用asObservable(),就要额外添加.observeOn(MainScheduler.instance)和.shareReplay(1),
Driver是属于Rxcocoa库,是对Observable进行了一些封装,Observable是属于RxSwift库
viewModel.signupEnabled
.drive(onNext: { [weak self] valid in
self?.signupOutlet.isEnabled = valid
self?.signupOutlet.alpha = valid ? 1.0 : 0.5
})
.addDisposableTo(disposeBag)
监听viewModel.signupEnabled的值变化,signupEnabled的类型为
Driver<Bool>,那么valid的类型就为Bool,根据valid来设置按钮的状态
viewModel.validatedUsername
.drive(usernameValidationOutlet.rx.validationResult)
.addDisposableTo(disposeBag)
viewModel.validatedPassword
.drive(passwordValidationOutlet.rx.validationResult)
.addDisposableTo(disposeBag)
viewModel.validatedPasswordRepeated
.drive(repeatedPasswordValidationOutlet.rx.validationResult)
.addDisposableTo(disposeBag)
viewModel.signingIn
.drive(signingUpOulet.rx.isAnimating)
.addDisposableTo(disposeBag)
将viewModel.validatedUsername和UILabel的validationResult绑定, validationResult是UILabel自定义的Rx扩展,源码如下:
extension Reactive where Base: UILabel {
var validationResult: UIBindingObserver<Base, ValidationResult> {
return UIBindingObserver(UIElement: base) { label, result in
label.textColor = result.textColor
label.text = result.description
}
}
}
let tapBackground = UITapGestureRecognizer()
tapBackground.rx.event
.subscribe(onNext: { [weak self] _ in
self?.view.endEditing(true)
})
.addDisposableTo(disposeBag)
view.addGestureRecognizer(tapBackground)
以上是新建一个tap手势并使用Rx对手势的监听来实现相应的功能
GithubSignupViewModel2.swift
init(
input: (
username: Driver<String>,
password: Driver<String>,
repeatedPassword: Driver<String>,
loginTaps: Driver<Void>
),
dependency: (
API: GitHubAPI,
validationService: GitHubValidationService,
wireframe: Wireframe
)
)
首先是初始化接受一个两个元组作为参数
validatedUsername = input.username
.distinctUntilChanged() //demo重复检查
.flatMapLatest { username in
return validationService.validateUsername(username)
.asDriver(onErrorJustReturn: .failed(message: "Error contacting server"))
}
validatedPassword = input.password
.map { password in
return validationService.validatePassword(password)
}
对username和password的值进行处理,然后返回Driver<ValidationResult>的结果为后面处理做准备
validatedPasswordRepeated = Driver.combineLatest(input.password, input.repeatedPassword, resultSelector: validationService.validateRepeatedPassword)
Driver.combineLatest将两个流合并成一个流,通过对源码的阅读发现combineLatest最多支持8路流合并成一路流, resultSelector提供多路流合并的方法,这里可以写成闭包的形式,也可以直接传入一个处理函数.
let signingIn = ActivityIndicator()
ActivityIndicator提供检测网络访问状态的方法
self.signingIn = signingIn.asDriver()
提供网络访问的状态监听
.trackActivity(signingIn)
在网络请求时添加到上面的方法,可以监听网络状态,网络开始访问时返回true,网络访问结束时返回false
signedIn = input.loginTaps.withLatestFrom(usernameAndPassword)
.flatMapLatest { (username, password) in
return API.signup(username, password: password)
.trackActivity(signingIn)
.asDriver(onErrorJustReturn: false)
}
.flatMapLatest { loggedIn -> Driver<Bool> in
let message = loggedIn ? "Mock: Signed in to GitHub." : "Mock: Sign in to GitHub failed"
return wireframe.promptFor(message, cancelAction: "OK", actions: [])
// propagate original value
.map { _ in
loggedIn
}
.asDriver(onErrorJustReturn: false)
}
每次登录按钮点击的时候,利用.withLatestFrom(usernameAndPassword)从usernameAndPassword中获取用户名和密码,然后传给网络请求进行访问,然后显示登录结果,并返回登录结果给上层处理
signupEnabled = Driver.combineLatest(
validatedUsername,
validatedPassword,
validatedPasswordRepeated,
signingIn
) { username, password, repeatPassword, signingIn in
username.isValid &&
password.isValid &&
repeatPassword.isValid &&
!signingIn
}
.distinctUntilChanged()
通过validatedUsername, validatedPassword, validatedPasswordRepeated, signingIn四个事件流的状态来决定signupEnabled的状态,也就是决定登录按钮的状态