RxSwift UI交互 - II

對(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)行;

image

其次,在這個(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);
image

第三,我們?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還添加了一些需要用到的圖片資源;

image

這就是我們對(duì)接下來(lái)內(nèi)容的準(zhǔn)備工作,了解清楚之后,我們就可以開工了。


校驗(yàn)UIDatePicker

首先來(lái)處理UIDatePicker,給它添加邊框的代碼和UITextField是類似的。

RxSwiftUIDatePicker添加了一個(gè)擴(kuò)展叫做rx_date,我們可以直接把這個(gè)Observable<NSDate>映射成一個(gè)Observable<Bool>表示輸入的生日是否合法。

viewDidLoad方法里,添加下面的代碼:

let birthdayObservable = self.birthday.rx_date.map {
    InputValidator.isValidDate($0)
}

然后,把得到的結(jié)果進(jìn)一步mapUIColor,并且訂閱它:

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ì)有綠色的邊框。

image

理解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;
image
  • Variable作為Observable,它還可以被其他的Observer訂閱,每當(dāng)有新訂閱的時(shí)候,它就會(huì)發(fā)送最近一次發(fā)生的事件以及以后陸續(xù)會(huì)發(fā)生的事件;
image
  • 而當(dāng)source observable發(fā)生.Complete.Error時(shí),Variable會(huì)向observer轉(zhuǎn)發(fā)對(duì)應(yīng)的事件,并自動(dòng)被回收;
image

介紹了理論之后,我們來(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ō)明:

  1. 我們使用map方法把點(diǎn)擊事件變成了一個(gè)值為Gender enum事件;
  2. 使用bindTo訂閱了map后的事件,在這里,bindTosubscribe是等價(jià)的,只是當(dāng)我們想表達(dá)“把一個(gè)值綁定給Variable這樣的語(yǔ)義時(shí)”,bindTosubscribe的表意更明確一些;

這樣,當(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方法,把birthdayObservablegenderBtnObservable合并起來(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ō)明:

  1. 我們?cè)僖淮问褂昧?code>bindTo代替了subscribe用于表達(dá)“綁定”的語(yǔ)義;
  2. rx_enabledRxSwiftUIButton添加的另外一個(gè)擴(kuò)展,表示按鈕是否啟用;

至此,我們就實(shí)現(xiàn)了兩個(gè)功能:

  1. 模擬了二選一的按鈕效果;
  2. 當(dāng)UI上所有控件都有合法值時(shí),啟用update按鈕;

Command + R編譯執(zhí)行,我們就能看到對(duì)應(yīng)的效果了:

image

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 在之前用Objective-C語(yǔ)言做項(xiàng)目的時(shí)候,我習(xí)慣性的會(huì)利用MVVM模式去架構(gòu)項(xiàng)目,在框架Reactiv...
    Tangentw閱讀 21,412評(píng)論 32 124
  • Observer(觀察者)、Observable(可觀察序列) ? 核心理解就是一個(gè)觀察者(Observer)訂...
    Harely閱讀 395評(píng)論 0 0
  • 函數(shù)式編程 本文介紹了函數(shù)響應(yīng)式編程(FRP)以及 RxSwift 的一些內(nèi)容, 源自公司內(nèi)部的一次分享. 不變狀...
    icetime17閱讀 1,132評(píng)論 0 0
  • 本章將向你介紹另一個(gè)框架,它是原生RxSwift庫(kù)的一部分:RxCocoa。 RxCocoa全平臺(tái)通用。每個(gè)平臺(tái)有...
    大灰很閱讀 955評(píng)論 3 2
  • 內(nèi)容概覽: Rx Marble Diagrams(寶石圖) 關(guān)鍵概念 Event - 事件 Observable ...
    FicowShen閱讀 3,491評(píng)論 0 8

友情鏈接更多精彩內(nèi)容