對(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ì)變大;反之則變?。?/li> - 最后,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)備工作,了解清楚之后,我們就可以開工了。
校驗(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)的效果了:
