對(duì)初始項(xiàng)目的改動(dòng)
為了演示RxSwift的用法靖苇,我們對(duì)上一個(gè)視頻用到的項(xiàng)目徽级,做了以下改動(dòng):
首先,給Sign Up添加了一個(gè)Segue请垛,點(diǎn)擊后,會(huì)切換到一個(gè)用戶提交各種信息的UI洽议,我們所有要演示的交互都在這個(gè)新的UI上進(jìn)行宗收;
其次,在這個(gè)新的UI里:
-
UIDatePicker
用于設(shè)置生日亚兄、當(dāng)輸入的生日小于當(dāng)天時(shí)混稽,我們會(huì)在這個(gè)picker外圍顯示一個(gè)綠框; - Male和Female是兩個(gè)按鈕,它加載了兩個(gè)
UIImage
用于模擬二選一的效果匈勋。只有這兩個(gè)內(nèi)容正確之后礼旅,我們才啟用底部的update按鈕,否則就禁用它洽洁; - “Know swift”是一個(gè)
UISwitch
痘系,表示用戶是否了解Swift; - 下面的
UISlider
則表示用戶對(duì)Swift的熟悉程度饿自; - 接下來(lái)是一個(gè)
UIStepper
汰翠,用于設(shè)置對(duì)Swift的興趣,當(dāng)點(diǎn)擊加號(hào)時(shí)昭雌,紅心會(huì)變大复唤;反之則變小烛卧; - 最后佛纫,submit按鈕只有在所有UI控件都有正確值的時(shí)候才啟用,否則是禁用狀態(tài)总放;
第三呈宇,我們?yōu)檫@個(gè)新的UI定義了一個(gè)AboutYouViewController
,它里面有我們需要的IBOutlet以及DisposeBag
间聊;
class AboutYouViewController: UIViewController {
@IBOutlet weak var birthday: UIDatePicker!
@IBOutlet weak var male: UIButton!
@IBOutlet weak var female: UIButton!
@IBOutlet weak var knowSwift: UISwitch!
@IBOutlet weak var swiftLevel: UISlider!
@IBOutlet weak var passionToLearn: UIStepper!
@IBOutlet weak var heartHeight: NSLayoutConstraint!
@IBOutlet weak var update: UIButton!
var bag: DisposeBag! = DisposeBag()
// Omit for simplicity…
}
第四攒盈,給InputValidator
添加了一個(gè)新的方法,用于驗(yàn)證UIDatePicker
輸入的日期是否小于當(dāng)天哎榴;
class func isValidDate(date: NSDate) -> Bool {
let calendar = NSCalendar.currentCalendar()
let compare = calendar.compareDate(date,
toDate: NSDate(),
toUnitGranularity: .Day)
return compare == .OrderedAscending
}
最后型豁,我們?cè)?code>InputValidator還添加了一些需要用到的圖片資源;
這就是我們對(duì)接下來(lái)內(nèi)容的準(zhǔn)備工作尚蝌,了解清楚之后迎变,我們就可以開(kāi)工了。
校驗(yàn)UIDatePicker
首先來(lái)處理UIDatePicker
飘言,給它添加邊框的代碼和UITextField
是類似的衣形。
RxSwift給UIDatePicker
添加了一個(gè)擴(kuò)展叫做rx_date
,我們可以直接把這個(gè)Observable<NSDate>
映射成一個(gè)Observable<Bool>
表示輸入的生日是否合法姿鸿。
在viewDidLoad
方法里谆吴,添加下面的代碼:
let birthdayObservable = self.birthday.rx_date.map {
InputValidator.isValidDate($0)
}
然后,把得到的結(jié)果進(jìn)一步map
成UIColor
苛预,并且訂閱它:
birthdayObservable.map {
$0 ? UIColor.greenColor() : UIColor.clearColor()
}.subscribeNext {
self.birthday.layer.borderColor = $0.CGColor
}.addDisposableTo(self.bag)
最后句狼,別忘記設(shè)置borderWidth
屬性:
self.birthday.layer.borderWidth = 1
完成后,Command + R
編譯執(zhí)行热某,就可以看到結(jié)果了腻菇,只有在設(shè)置當(dāng)天以前的日期時(shí)胳螟,UIDatePicker
才會(huì)有綠色的邊框。
理解Rx編程中的Subject
接下來(lái)筹吐,我們來(lái)處理選擇性別的按鈕糖耸。由于默認(rèn)情況下,沒(méi)有任何一個(gè)性別被選中丘薛,因此嘉竟,實(shí)際上我們要處理的邏輯有兩個(gè):
- 用戶選擇了一個(gè)性別,表示按鈕被點(diǎn)擊了洋侨;
- 用戶具體選擇的是哪個(gè)性別周拐,我們要根據(jù)這個(gè)選擇加載正確的
UIImage
;
我們來(lái)逐步實(shí)現(xiàn)它凰兑。
首先,添加一個(gè)enum
审丘,表示用戶選擇的性別:
enum Gender {
case notSelected
case male
case female
}
其次吏够,我們需要一個(gè)observer
,用來(lái)訂閱按鈕的點(diǎn)擊事件滩报,這樣锅知,我們就知道按鈕被點(diǎn)擊了,并以此作為啟用udpate按鈕的依據(jù)之一(這跟我們上個(gè)視頻中用到的例子是相同的)脓钾。
但是售睹,我們還需要這個(gè)observer
是一個(gè)Observable
,因?yàn)槲覀冇嗛喫裳担⒏鶕?jù)用戶點(diǎn)擊的按鈕設(shè)置按鈕圖片昌妹,怎么做呢?
在Reactive編程里握截,有一個(gè)概念叫做Subject飞崖,它是一類對(duì)象的統(tǒng)稱。這類對(duì)象既可以做Observer
谨胞,又可以做Observable
固歪。大家可以在Reactive.io找到它的詳細(xì)定義。
在RxSwift里胯努,我們使用其中一個(gè)叫做Variable
的Subject牢裳,簡(jiǎn)單用圖來(lái)表示,它是這樣的:
-
Variable
作為Observer叶沛,它可以訂閱一個(gè)Observable蒲讯,我們管這個(gè)Observable叫做source observable;
-
Variable
作為Observable恬汁,它還可以被其他的Observer訂閱伶椿,每當(dāng)有新訂閱的時(shí)候辜伟,它就會(huì)發(fā)送最近一次發(fā)生的事件以及以后陸續(xù)會(huì)發(fā)生的事件;
- 而當(dāng)source observable發(fā)生
.Complete
或.Error
時(shí)脊另,Variable
會(huì)向observer轉(zhuǎn)發(fā)對(duì)應(yīng)的事件导狡,并自動(dòng)被回收;
介紹了理論之后偎痛,我們來(lái)看代碼旱捧。先定義一個(gè)Variable<Gender>
:
let genderSelection = Variable<Gender>(.notSelected)
讓它先去訂閱按鈕的點(diǎn)擊事件:
self.male.rx_tap.map {
return Gender.male
}
.bindTo(genderSelection)
.addDisposableTo(self.bag)
self.female.rx_tap.map {
return Gender.female
}
.bindTo(genderSelection)
.addDisposableTo(self.bag)
這里有兩點(diǎn)需要說(shuō)明:
- 我們使用
map
方法把點(diǎn)擊事件變成了一個(gè)值為Genderenum
事件; - 使用
bindTo
訂閱了map
后的事件踩麦,在這里枚赡,bindTo
和subscribe
是等價(jià)的,只是當(dāng)我們想表達(dá)“把一個(gè)值綁定給Variable
這樣的語(yǔ)義時(shí)”谓谦,bindTo
比subscribe
的表意更明確一些贫橙;
這樣,當(dāng)不同的按鈕被點(diǎn)擊時(shí)反粥,genderSelection
就有不同的值了卢肃。接下來(lái),我們要讓genderSelection
是一個(gè)observable才顿,并根據(jù)它的值來(lái)為按鈕設(shè)置圖片:
genderSelection.asObservable().subscribeNext({
switch $0 {
case .male:
self.male.setImage(UIImage(named: "check"),
forState: .Normal)
self.female.setImage(UIImage(named: "uncheck"),
forState: .Normal)
case .female:
self.male.setImage(UIImage(named: "uncheck"),
forState: .Normal)
self.female.setImage(UIImage(named: "check"),
forState: .Normal)
default:
break;
}
}).addDisposableTo(self.bag)
在上面的代碼里莫湘,我們使用genderSelection.asObservable()
把一個(gè)Variable
明確轉(zhuǎn)換成了observable。這樣郑气,我們就能使用subscribeNext
來(lái)訂閱它了幅垮,在上面的代碼里$0
的值是我們之前定義的Gender enum
,我們只要根據(jù)用戶點(diǎn)擊的選擇尾组,為按鈕設(shè)置正確的圖片就好了忙芒。
最后,還有一個(gè)工作演怎。當(dāng)用戶設(shè)置了生日和性別之后匕争,UI上所有控件的值就都有合法值了,我們添加啟用Submit按鈕的代碼爷耀,這和我們?cè)谏蟼€(gè)視頻中的代碼是類似的甘桑。
我們先把genderSelection
變成一個(gè)Observable<Bool>
:
let genderBtnObservable = genderSelection.asObservable().map {
return $0 != .notSelected ? true : false
}
然后,使用combineLatest
方法歹叮,把birthdayObservable
和genderBtnObservable
合并起來(lái)跑杭,再變成一個(gè)Observable<Bool>
:
Observable.combineLatest(birthdayObservable, genderBtnObservable) {
return [$0, $1]
}.map {
$0.reduce(true, combine: { $0 && $1 })
}
然后,訂閱這個(gè)合并的結(jié)果咆耿,把它和update按鈕的rx_tap
”綁定”起來(lái):
Observable.combineLatest(birthdayObservable, genderBtnObservable) {
return [$0, $1]
}.map {
$0.reduce(true, combine: { $0 && $1 })
}
.bindTo(self.update.rx_enabled)
.addDisposableTo(self.bag)
這里有兩點(diǎn)要說(shuō)明:
- 我們?cè)僖淮问褂昧?code>bindTo代替了
subscribe
用于表達(dá)“綁定”的語(yǔ)義德谅; -
rx_enabled
是RxSwift給UIButton
添加的另外一個(gè)擴(kuò)展,表示按鈕是否啟用萨螺;
至此窄做,我們就實(shí)現(xiàn)了兩個(gè)功能:
- 模擬了二選一的按鈕效果愧驱;
- 當(dāng)UI上所有控件都有合法值時(shí),啟用update按鈕椭盏;
按Command + R
編譯執(zhí)行组砚,我們就能看到對(duì)應(yīng)的效果了: