在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<Bool>
類型
對于賬號和密碼輸入正確與否的一個顯示
//賬號密碼輸入的正確與否 綁定到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<Element> sequence
,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<Result> {
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<Result> {
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<Result> {
//根據網絡返回的數據進行 返回
if userName.characters.count > 0 && password.characters.count > 0{
return .just(.ok(message:"登錄成功"))
}
return .just(.failed(message:"密碼或登錄名錯誤"))
}
2.接下來就是Model類
創建一個swift文件,在類中聲明
//輸出 這是輸出的一個定義
let userNameUsable:Driver<Result>
let userPasswordAble:Driver<Result>
let loginButtonEnabled :Driver<Bool>
let loginResult:Driver<Result>
初始化函數如下
init(input:(userName:Driver<String>,password:Driver<String>,loginTaps:Driver<Void>),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<DriverSharingStrategy, Result> 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)
實現效果如下