在iOS学习中登录注册是一个万能的可以拿出来实战的demo。接下来我们就从登录开始入手,PS:如果你对RXSwift中的概念和一些常用的函数不清楚可以参考这篇文章(可能打开比较慢请耐心等待)。开始直接上代码。先看一下我们要实现的效果。
分析实现:
1.在还没有输入的时候,显示提醒信息
2.输入账号和密码正确的时候隐藏提示信息
2.在账号和密码都输入的时候登录按钮可以点击
1.直接在storyBoard中创建简单的登录界面
2.关联好对应的属性,接下来引入我们今天的重点对象
import RxSwift
import RxCocoa
创建一个disposeBag来盛放我们需要管理的资源,然后把新建的监听都放进去,会在适当的时候销毁这些资源。
let disposeBag = DisposeBag()
3.接下来开始对事件的判断和绑定事件
//判断账号的输入是否可用
let accountValid:Observable = accountField.rx.text.orEmpty.map{ value in
return value.characters.count >= 6
}
//判断密码的输入是否可用
let passwordValid:Observable = passwordField.rx.text.orEmpty.map{ value in
return value.characters.count >= 6
}
上面orEmpty
是判断当前字符串是否为空的,在RXSwift中已经处理了为nil的情况,map
函数是在事件流转换的时候,重新生成另一个事件流,在这里是把一个文字事件流映射成一个bool事件流,accountValid
和passwordValid
都是Observable
类型
对于账号和密码输入正确与否的一个显示
//账号密码输入的正确与否 绑定到infoLabel的hidden属性上
//绑定显示
accountValid.bind(to: accountInfoLabel.rx.isHidden).addDisposableTo(disposeBag)
passwordValid.bind(to: passwordInfoLabel.rx.isHidden).addDisposableTo(disposeBag)
接着就是对于登录按钮的是否可点击的绑定
//登录按钮的可用与否
let loginObserver = Observable.combineLatest(accountValid,passwordValid){(account,password) in
account && password
}
//绑定按钮
loginObserver.bind(to: loginBtn.rx.isEnabled).addDisposableTo(disposeBag)
loginObserver.subscribe(onNext: { [unowned self] valid in
self.loginBtn.alpha = valid ? 1 : 0.5
}).disposed(by: disposeBag)
上面的将账号和密码输入的值与按钮的enable
属性相关联,当accountValid
为true
,并且passwordValid
也为true
时,按钮才可点击,同时也修改了按钮的透明度变化
接下来就是按钮点击事件的判断以及对应的方法的执行
loginBtn.rx.tap
.asObservable()
.withLatestFrom(loginObserver)
.do(onNext: {
[unowned self]_ in
self.loginBtn.isEnabled = false
self.view.endEditing(true)
})
.subscribeOn(MainScheduler.instance)//主线程
.subscribe(onNext: {[unowned self]isLogin in
self.showAlert(message: "开始点击")
self.loginBtn.isEnabled = true
})
.addDisposableTo(disposeBag)//开始释放
按钮的点击事件中绑定的是loginObserver
最新的一个流操作,do(onNext)
函数是在执行之前对按钮的一个限定,比如网络请求延迟,按钮点击多次,我在按钮第一次点击的时候,就禁用按钮,等到网络请求成功或者失败返回信息的时候再修改按钮可点击的状态,.subscribeOn
函数是指定事件流在那个线程中执行,这里指定的是主线程。subscribe(onNext…………
这是点击按钮之后执行方法的闭包。 简写也可以写成这个样子哦,这个只是简单处理按钮的点击事件
loginBtn.rx.tap
.subscribe(onNext: {[unowned self]isLogin in
self.showAlert(message: "开始点击")
})
.addDisposableTo(disposeBag)//开始释放
最后是alertView的一个弹出视图
fileprivate func showAlert(message:String) {
let action = UIAlertAction.init(title: "确定", style: .default, handler: nil)
let alertView = UIAlertController.init(title: nil, message: message, preferredStyle: .alert)
alertView.addAction(action)
present(alertView, animated: true, completion: nil)
}
以上只是一个简单的值绑定进行的判断,接下来我们要使用Observable和Driver去实现这个登录注册功能,下面实现的比较绕,请坐好车
接下来我们要使用Driver去实现登录功能。先说明一下Observable和Driver的一个简介。
RXSwift
RxSwift的核心是想是 Observable
,Observable
表示可监听或者可观察,也就是说RxSwift的核心思想是可监听的序列。并且,Observable sequence
可以接受异步信号,也就是说,信号是可以异步给监听者的
- Observable(ObservableType) 和 SequenceType类似
- ObservableType.subscribe 和 SequenceType.generate类似
- 由于RxSwift支持异步获得信号,所以用ObservableType.subscribe,这和indexGenerator.next()类似
本文把RxSwift中的序列的每一个Element成为信号,因为异步的Element是与时间相关的,称作信号更好理解一点。
Driver
Driver是RxSwift精心制作的,专门提供给UI层的一个接口。
利用Driver你可以
- 利用CoreData的模型来驱动UI
- 利用UI的状态来绑定其他UI的状态
Driver能够保证,在主线程上监听,因为UIKit不是需要在主线程上操作
使用Driver时UI布局和上面一样都是一个简单的登录界面,接下来我们使用MVVM来构建一个登录界面的逻辑处理。
1.新建一个Service类处理用户名,密码和登录按钮的状态 ,新建一个Model类处理绑定事件
首先是Service类的一个创建,用户输入账号的时候有三种状态
enum Result {
case ok(message:String)//输入正确
case empty//输入为空
case failed(message:String)//输入不合法
}
用这三种状态去判断所输入的账号和密码是否是合法的
static let instance = ValidationService() // 定义一个单例
let minCharactersCount = 6 //最少字符限制
private init(){}
//返回一个Observable对象,这个请求过程要被监听
//MARK: 登录用户名验证
func LoginUserNameValid(_ userName:String) -> Observable {
if userName.characters.count == 0 {
return .just(.empty);
}
if userName.characters.count < minCharactersCount {
return .just(.failed(message: "用户名至少是6个字符"))
}
return .just(.ok(message:"用户名可用"))
}
func LoginPasswordValid(_ password:String) -> Observable {
if password.characters.count == 0 {
return .just(.empty)
}
if password.characters.count < minCharactersCount {
return .just(.failed(message:"密码长度至少6个字符"))
}
return .just(.ok(message:"密码可用"))
}
//开始登录,定义的一个登录事件,在这里面进行网络回调
func login(_ userName:String,password:String) -> Observable {
//根据网络返回的数据进行 返回
if userName.characters.count > 0 && password.characters.count > 0{
return .just(.ok(message:"登录成功"))
}
return .just(.failed(message:"密码或登录名错误"))
}
2.接下来就是Model类
创建一个swift文件,在类中声明
//输出 这是输出的一个定义
let userNameUsable:Driver
let userPasswordAble:Driver
let loginButtonEnabled :Driver
let loginResult:Driver
初始化函数如下
init(input:(userName:Driver,password:Driver,loginTaps:Driver),service:ValidationService) {
//用户名是否合法
userNameUsable = input.userName
.flatMapLatest{ username in
return service.LoginUserNameValid(username)
.asDriver(onErrorJustReturn: .failed(message: "连接服务失败"))}
//密码是否合法
userPasswordAble = input.password
.flatMapLatest{ password in
return service.LoginPasswordValid(password)
.asDriver(onErrorJustReturn: .failed(message: "密码填写错误"))
}
let userNameAndPassword = Driver.combineLatest(input.userName,input.password){($0,$1)}
//按钮点击的触发事件
loginResult = input.loginTaps
.withLatestFrom(userNameAndPassword)
.flatMapLatest{ (arg) -> SharedSequence in
let (userName, password) = arg
return service.login(userName, password: password).asDriver(onErrorJustReturn: .failed(message:"连接服务失败"))
}
//按钮是否可以点击
loginButtonEnabled = input.password
.map{$0.characters.count > 0}
.asDriver()
}
3.在ViewController中初始化model类 进行事件的绑定
let viewModel = LoginViewModel.init(
input: (
userName: accountField.rx.text.orEmpty.asDriver(),
password: passwordField.rx.text.orEmpty.asDriver(),
loginTaps: loginBtn.rx.tap.asDriver()),
service: ValidationService.instance
)
viewModel.userNameUsable
.drive(accountInfoLabel.rx.validationResult)
.addDisposableTo(disposeBag)
viewModel.userPasswordAble
.drive(passwordInfoLabel.rx.validationResult)
.addDisposableTo(disposeBag)
viewModel.loginButtonEnabled
.drive(onNext: { [unowned self] valid in
self.loginBtn.isEnabled = valid
self.loginBtn.alpha = valid ? 1 : 0.5
})
.addDisposableTo(disposeBag)
viewModel.loginResult
.drive(onNext: { [unowned self] result in
switch result{
case .empty:
self.showAlert(message: "")
case let .ok(message):
print(message)
//开始进行跳转
self.showAlert(message: message)
case let .failed(message):
self.showAlert(message: message)
}
})
.addDisposableTo(disposeBag)
实现效果如下