目錄
一、首先我們看看RxCocoa做了啥
? 1豌注、UIView
? 2、UILabel
? 3灯萍、UIImageView
? 4轧铁、UIButton
? 5、UITextField
? 6竟稳、UIScrollView
? 7属桦、UITableView
? 8、UICollectionView
二他爸、然后我們寫個(gè)MVVM的小案例
? 1聂宾、不使用RxSwift時(shí)的MVVM
? 2、使用RxSwift時(shí)的MVVM
一诊笤、首先我們看看RxCocoa做了啥
為了幫助我們更加簡(jiǎn)單優(yōu)雅地實(shí)現(xiàn)ViewModel和View的雙向綁定系谐,RxCocoa已經(jīng)幫我們把UIKit框架里常用控件的常用屬性都搞成了Observable或Binder、有的屬性甚至是Subjects,這樣有的屬性就可以發(fā)出事件(以便讓數(shù)據(jù)監(jiān)聽(tīng))纪他,有的屬性就可以監(jiān)聽(tīng)Observable——即數(shù)據(jù)鄙煤,有的屬性既可以發(fā)出事件、也可以監(jiān)聽(tīng)Observable茶袒,因此在實(shí)際開(kāi)發(fā)中UI這邊兒直接拿現(xiàn)成的用就行了梯刚,通常情況下我們只需要把數(shù)據(jù)定義成Subjects——這樣數(shù)據(jù)就可以發(fā)出事件(以便讓UI監(jiān)聽(tīng))、也可以監(jiān)聽(tīng)Observable——即UI薪寓。
1亡资、UIView
UIView的rx.backgroundColor
、rx.alpha
向叉、rx.isHidden
锥腻、rx.isUserInteractionEnabled
屬性都是Binder,所以它們可以監(jiān)聽(tīng)Observable母谎。
-
rx.backgroundColor
屬性
rx.backgroundColor
屬性是對(duì)傳統(tǒng)方式view.setBackgroundColor(...)
方法的封裝瘦黑,我們可以用它來(lái)設(shè)置view的背景顏色。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var customView: UIView!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置customView的背景顏色
//
// 1奇唤、observable負(fù)責(zé)發(fā)出事件幸斥,事件上掛的數(shù)據(jù)就是一個(gè)顏色
// 2、customView的rx.backgroundColor屬性就是一個(gè)binder冻记,所以它可以監(jiān)聽(tīng)observable睡毒,當(dāng)它收到observable發(fā)出的事件時(shí),就會(huì)把事件上掛的顏色拿下來(lái)真正賦值給customView的backgroundColor屬性冗栗。還記得我們自己是怎么創(chuàng)建Binder的吧演顾,可以翻回去看一下,RxCocoa底層就是那么實(shí)現(xiàn)的
let observable = Observable.just(UIColor.red)
let binder = customView.rx.backgroundColor
observable.bind(to: binder).disposed(by: bag)
}
}
-
rx.alpha
屬性
rx.alpha
屬性是對(duì)傳統(tǒng)方式view.setAlpha(...)
方法的封裝隅居,我們可以用它來(lái)設(shè)置view的透明度钠至。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var customView: UIView!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置customView的透明度
Observable.just(0.618)
.bind(to: customView.rx.alpha)
.disposed(by: bag)
}
}
-
rx.isHidden
屬性
rx.isHidden
屬性是對(duì)傳統(tǒng)方式view.setHidden(...)
方法的封裝,我們可以用它來(lái)設(shè)置view是否隱藏胎源。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var customView: UIView!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置customView是否隱藏
Observable.just(true)
.bind(to: customView.rx.isHidden)
.disposed(by: bag)
}
}
-
rx.isUserInteractionEnabled
屬性
rx.isUserInteractionEnabled
屬性是對(duì)傳統(tǒng)方式view.setUserInteractionEnabled(...)
方法的封裝棉钧,我們可以用它來(lái)設(shè)置view是否能夠處理用戶交互。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var customView: UIView!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置customView是否能夠處理用戶交互
Observable.just(false)
.bind(to: customView.rx.isUserInteractionEnabled)
.disposed(by: bag)
}
}
2涕蚤、UILabel
UILabel的rx.text
宪卿、rx.attributedText
屬性都是Binder,所以它們可以監(jiān)聽(tīng)Observable万栅。
-
rx.text
屬性
rx.text
屬性是對(duì)傳統(tǒng)方式label.setText(...)
方法的封裝佑钾,我們可以用它來(lái)設(shè)置label的文本。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置label的文本
Observable.just("Hello RxSwift")
.bind(to: label.rx.text)
.disposed(by: bag)
}
}
-
rx.attributedText
屬性
rx.attributedText
屬性是對(duì)傳統(tǒng)方式label.setAttributedText(...)
方法的封裝烦粒,我們可以用它來(lái)設(shè)置label的屬性文本休溶。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置label的屬性文本
Observable.just("Hello RxSwift")
.map({ element in
let attributedString = NSAttributedString(string: element, attributes: [
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
NSAttributedString.Key.underlineColor: UIColor.red,
])
return attributedString
})
.bind(to: label.rx.attributedText)
.disposed(by: bag)
}
}
3代赁、UIImageView
UIImageView的rx.image
屬性是Binder,所以它可以監(jiān)聽(tīng)Observable。
-
rx.image
屬性
rx.image
屬性是對(duì)傳統(tǒng)方式imageView.setImage(...)
方法的封裝,我們可以用它來(lái)設(shè)置imageView的圖片卷仑。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置imageView的圖片
Observable<Int>.timer(.seconds(0), period: .milliseconds(167), scheduler: MainScheduler.instance)
.map({ element in
let imageName = "idle_\(element)" // 映射出圖片名稱
let image = UIImage(named: imageName)!
return image
})
.bind(to: imageView.rx.image)
.disposed(by: bag)
}
}
4、UIButton
UIButton的rx.tap
屬性是Observable窖壕,所以它可以發(fā)出事件。
UIButton的rx.isEnabled
杉女、rx.isSelected
屬性都是Observer艇拍,所以它們可以監(jiān)聽(tīng)Observable。
UIButton的rx.controlEvent(...)
方法的返回值是Observable宠纯,所以它可以發(fā)出事件。
UIButton的rx.title(for: ...)
层释、rx.image(for: ...)
婆瓜、rx.backgroundImage(for: ...)
方法的返回值都是Binder,所以它們的返回值可以監(jiān)聽(tīng)Observable贡羔。
-
rx.tap
屬性
rx.tap
屬性是對(duì)傳統(tǒng)方式button.addTarget(..., action: #selector(...), for: .touchUpInside)
方法的封裝廉白,我們可以用它來(lái)給button添加touchUpInside狀態(tài)下的點(diǎn)擊事件。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)給button添加touchUpInside狀態(tài)下的點(diǎn)擊事件
button.rx.tap
.subscribe { _ in
print("按鈕被點(diǎn)擊了")
}
.disposed(by: bag)
}
}
-
rx.isEnabled
屬性
rx.isEnabled
屬性是對(duì)傳統(tǒng)方式button.setEnabled(...)
方法的封裝乖寒,我們可以用它來(lái)設(shè)置button是否可以點(diǎn)擊猴蹂。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置button是否可以點(diǎn)擊
Observable<Int>.timer(.seconds(0), period: .seconds(1), scheduler: MainScheduler.instance)
.map({ element in
let value = element % 2 == 0 // 映射為bool值
return value
})
.bind(to: button.rx.isEnabled)
.disposed(by: bag)
}
}
-
rx.isSelected
屬性
rx.isSelected
屬性是對(duì)傳統(tǒng)方式button.setSelected(...)
方法的封裝,我們可以用它來(lái)設(shè)置button是否處于選中狀態(tài)楣嘁。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置button是否可以點(diǎn)擊
Observable<Int>.timer(.seconds(0), period: .seconds(1), scheduler: MainScheduler.instance)
.map({ element in
let value = element % 2 == 0 // 映射為bool值
return value
})
.bind(to: button.rx.isSelected)
.disposed(by: bag)
}
}
-
rx.controlEvent(...)
方法
rx.controlEvent(...)
方法是對(duì)傳統(tǒng)方式button.addTarget(..., action: ..., for: ...)
方法的封裝磅轻,我們可以用它來(lái)給button添加任意狀態(tài)下的點(diǎn)擊事件,上面的rx.tap
屬性底層就是通過(guò)這個(gè)方法實(shí)現(xiàn)的逐虚,只不過(guò)鎖死了touchUpInside狀態(tài)聋溜。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)給button添加任意狀態(tài)下的點(diǎn)擊事件
button.rx.controlEvent(.touchDown)
.subscribe { _ in
print("touchDown")
}
.disposed(by: bag)
}
}
-
rx.title(for: ...)
方法
rx.title(for: ...)
方法是對(duì)傳統(tǒng)方式button.setTitle(..., for: ...)
方法的封裝,我們可以用它來(lái)給button設(shè)置不同狀態(tài)下的文本叭爱。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)給button設(shè)置不同狀態(tài)下的文本
Observable.just("正常狀態(tài)下的文本")
.subscribe(button.rx.title(for: .normal))
.disposed(by: bag)
Observable.just("高亮狀態(tài)下的文本")
.subscribe(button.rx.title(for: .highlighted))
.disposed(by: bag)
}
}
-
rx.image(for: ...)
方法
rx.image(for: ...)
方法是對(duì)傳統(tǒng)方式button.setImage(..., for: ...)
方法的封裝撮躁,我們可以用它來(lái)給button設(shè)置不同狀態(tài)下的圖片。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)給button設(shè)置不同狀態(tài)下的圖片
Observable.just(UIImage(named: "idle_0"))
.subscribe(button.rx.image(for: .normal))
.disposed(by: bag)
Observable.just(UIImage(named: "idle_59"))
.subscribe(button.rx.image(for: .highlighted))
.disposed(by: bag)
}
}
-
rx.backgroundImage(for: ...)
方法
rx.backgroundImage(for: ...)
方法是對(duì)傳統(tǒng)方式button.setBackgroundImage(..., for: ...)
方法的封裝买雾,我們可以用它來(lái)給button設(shè)置不同狀態(tài)下的背景圖片把曼。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)給button設(shè)置不同狀態(tài)下的圖片
Observable.just(UIImage(named: "idle_1"))
.subscribe(button.rx.backgroundImage(for: .normal))
.disposed(by: bag)
Observable.just(UIImage(named: "idle_58"))
.subscribe(button.rx.backgroundImage(for: .highlighted))
.disposed(by: bag)
}
}
5、UITextField
UITextField的rx.text
屬性是Subjects漓穿,所以它既可以發(fā)出事件嗤军、也可以監(jiān)聽(tīng)Observable。
UITextField的rx.isSecureTextEntry
是Observer器净,所以它可以監(jiān)聽(tīng)Observable型雳。
UITextField的rx.controlEvent(...)
方法的返回值是Observable,所以它可以發(fā)出事件。
-
rx.text
屬性
rx.text
屬性充當(dāng)Observable角色時(shí)纠俭,一般被用來(lái)監(jiān)聽(tīng)textField內(nèi)容的改變(注意:通過(guò)這種方式來(lái)監(jiān)聽(tīng)textField內(nèi)容的改變時(shí)沿量,一打開(kāi)界面就算textField還沒(méi)成為第一響應(yīng)者也會(huì)觸發(fā)一次回調(diào))。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)監(jiān)聽(tīng)textField內(nèi)容的改變
textField.rx.text
.subscribe(onNext: { element in
print("textField的內(nèi)容改變了:\(element)")
})
.disposed(by: bag)
}
}
rx.text
屬性充當(dāng)Observer角色時(shí)冤荆,是對(duì)textField.setText(...)
方法的封裝朴则,我們可以用它來(lái)給textField設(shè)置內(nèi)容。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)給textField設(shè)置內(nèi)容
Observable.just("Hello RxSwift")
.subscribe(textField.rx.text)
.disposed(by: bag)
}
}
-
rx.isSecureTextEntry
屬性
rx.isSecureTextEntry
是對(duì)textField.setSecureTextEntry(...)
方法的封裝钓简,我們可以用它來(lái)設(shè)置textField是否密文輸入乌妒。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)設(shè)置textField是否密文輸入
Observable.just(true)
.subscribe(textField.rx.isSecureTextEntry)
.disposed(by: bag)
}
}
-
rx.controlEvent(...)
方法
rx.controlEvent(...)
方法是對(duì)傳統(tǒng)方式textField一堆代理方法的封裝,我們可以用它來(lái)監(jiān)聽(tīng)textField的不同狀態(tài)外邓,上面的rx.text
屬性底層就是通過(guò)這個(gè)方法實(shí)現(xiàn)的撤蚊,只不過(guò)鎖死了editingChanged狀態(tài)。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// 用RxSwift來(lái)監(jiān)聽(tīng)textField的不同狀態(tài)
textField.rx.controlEvent(.editingDidBegin)
.subscribe { _ in
// textField開(kāi)始編輯了损话、光標(biāo)開(kāi)始閃動(dòng)(textField成為第一響應(yīng)者)
print("textFieldDidBeginEditing")
}
.disposed(by: bag)
// 一打開(kāi)界面不會(huì)觸發(fā)侦啸,只有真正改變了內(nèi)容才會(huì)觸發(fā)
textField.rx.controlEvent(.editingChanged)
.subscribe { [weak self] _ in
// textField的內(nèi)容改變了
print("textField的內(nèi)容改變了:\(self.textField.text)")
}
.disposed(by: bag)
textField.rx.controlEvent(.editingDidEnd)
.subscribe { _ in
// textField結(jié)束編輯了、光標(biāo)停止閃動(dòng)
print("textFieldDidEndEditing")
}
.disposed(by: bag)
textField.rx.controlEvent(.editingDidEndOnExit)
.subscribe { _ in
// 點(diǎn)擊了鍵盤上的return鍵結(jié)束編輯丧枪,緊接著會(huì)觸發(fā)【textField結(jié)束編輯了光涂、光標(biāo)停止閃動(dòng)】
print("textFieldShouldReturn")
}
.disposed(by: bag)
}
}
6、UIScrollView
UIScrollView的rx.contentOffset
屬性是Subjects拧烦,所以它既可以發(fā)出事件忘闻、也可以監(jiān)聽(tīng)Observable。
UIScrollView的rx.willBeginDragging
恋博、rx.didScroll
齐佳、rx.didEndDragging
、rx.didEndDecelerating
屬性都是Observable债沮,所以它們可以發(fā)出事件重虑。
-
rx.contentOffset
屬性
rx.contentOffset
屬性充當(dāng)Observable角色時(shí),一般被用來(lái)監(jiān)聽(tīng)scrollView的滾動(dòng)(注意:通過(guò)這種方式來(lái)監(jiān)聽(tīng)scrollView的滾動(dòng)時(shí)秦士,一打開(kāi)界面就算不滾動(dòng)scrollView也會(huì)觸發(fā)一次回調(diào))缺厉。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.contentSize = CGSize(width: 0, height: 1000)
// 用RxSwift來(lái)監(jiān)聽(tīng)scrollView的滾動(dòng)
scrollView.rx.contentOffset
.subscribe(onNext: { contentOffset in
print("scrollView滾動(dòng)中:\(contentOffset)")
})
.disposed(by: bag)
}
}
rx.contentOffset
屬性充當(dāng)Observer角色時(shí),是對(duì)scrollView.setContentOffset(...)
方法的封裝隧土,我們可以用它來(lái)設(shè)置內(nèi)容scrollView的偏移量提针。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.contentSize = CGSize(width: 0, height: 1000)
// 用RxSwift來(lái)設(shè)置內(nèi)容scrollView的偏移量
Observable<Int>.timer(.seconds(0), period: .seconds(1), scheduler: MainScheduler.instance)
.map({ element in
let point = CGPoint(x: 0, y: 10 * element) // 映射出點(diǎn)
return point
})
.bind(to: scrollView.rx.contentOffset)
.disposed(by: bag)
}
}
-
rx.willBeginDragging
、rx.didScroll
曹傀、rx.didEndDragging
辐脖、rx.didEndDecelerating
屬性
這一堆屬性是對(duì)傳統(tǒng)方式scrollView一堆代理方法的封裝,我們可以用它們來(lái)監(jiān)聽(tīng)scrollView的滾動(dòng)皆愉。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.contentSize = CGSize(width: 0, height: 1000)
// 用RxSwift來(lái)監(jiān)聽(tīng)scrollView的滾動(dòng)
scrollView.rx.willBeginDragging
.subscribe { _ in
print("即將開(kāi)始拖拽scrollView")
}
.disposed(by: bag)
// 一打開(kāi)界面不會(huì)觸發(fā)嗜价,只有真正滾動(dòng)了scrollView才會(huì)觸發(fā)
scrollView.rx.didScroll
.subscribe { _ in
print("scrollView滾動(dòng)中:\(self.scrollView.contentOffset)")
}
.disposed(by: bag)
scrollView.rx.didEndDragging
.subscribe { decelerate in
if decelerate.element == false {
print("scrollView停止?jié)L動(dòng)");
} else {
print("已經(jīng)停止拖拽scrollView艇抠,但是scrollView由于慣性還在減速滾動(dòng)");
}
}
.disposed(by: bag)
// 但是光靠這個(gè)方法來(lái)判定scrollView停止?jié)L動(dòng)是有一個(gè)bug的,那就是當(dāng)我們的手指停止拖拽scrollView時(shí)久锥、按住屏幕不放手家淤、導(dǎo)致scrollView不滾動(dòng),是不會(huì)觸發(fā)這個(gè)方法的瑟由,而是會(huì)觸發(fā)scrollViewDidEndDragging:willDecelerate:方法絮重,所以嚴(yán)謹(jǐn)來(lái)判斷應(yīng)該靠它倆聯(lián)合
scrollView.rx.didEndDecelerating
.subscribe { _ in
print("scrollView停止?jié)L動(dòng)")
}
.disposed(by: bag)
}
}
7、UITableView
UITableView這塊兒歹苦,我們就不像上面那樣一個(gè)一個(gè)屬性或方法詳細(xì)說(shuō)了青伤,直接演示下怎么用,因?yàn)檫@塊兒的內(nèi)容實(shí)在太多了殴瘦,可以自己點(diǎn)進(jìn)去UITableView+Rx.swift
文件去看去分析狠角。
UITableView這塊兒,Observable通常有兩種蚪腋,一是tableView要顯示的數(shù)據(jù)——也就是說(shuō)我們得把tableView要顯示的數(shù)據(jù)給手動(dòng)搞成一個(gè)Observable擎厢,然后讓tableView的一堆東西——即Observer來(lái)監(jiān)聽(tīng)這個(gè)Observable就可以了,這樣數(shù)據(jù)發(fā)生變化時(shí)辣吃,tableView的顯示就會(huì)跟著自動(dòng)發(fā)生變化,非常符合數(shù)據(jù)驅(qū)動(dòng)UI的理念芬探;二是用戶對(duì)tableView做的操作——如點(diǎn)擊cell神得、插入cell、刪除cell偷仿、移動(dòng)cell等哩簿;其它的都是Observer。
- 實(shí)現(xiàn)單個(gè)分區(qū)的UITableView
1??我們不需要像傳統(tǒng)方式那樣實(shí)現(xiàn)numberOfSectionsInTableView:
代理方法告訴tableView有一個(gè)分區(qū)
2??我們也不需要像傳統(tǒng)方式那樣調(diào)用numberOfRowsInSection:
告訴tableView分區(qū)里有多少個(gè)cell
3??我們只需要實(shí)現(xiàn)cellForRowAtIndexPath:
這一個(gè)代理方法(是一個(gè)Observer)酝静,讓它來(lái)監(jiān)聽(tīng)數(shù)據(jù)(是一個(gè)Observable)就可以了节榜,RxSwift會(huì)自動(dòng)給我們搞好有一個(gè)分區(qū)、分區(qū)里有多少個(gè)cell這些事
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let bag = DisposeBag()
// tableView
lazy var tableView: UITableView = {
let tableView = UITableView(frame: CGRect.zero, style: .plain)
tableView.backgroundColor = UIColor.clear
return tableView
}()
// tableView要顯示的數(shù)據(jù)
//
// 不要定義成普通數(shù)組别智,要定義成Observable宗苍,讓它發(fā)出的事件里掛數(shù)組,這樣才能讓tableView監(jiān)聽(tīng)薄榛,達(dá)到數(shù)據(jù)驅(qū)動(dòng)UI的效果
var dataArray = Observable.just([
"張一",
"張二",
"張三",
])
override func viewDidLoad() {
super.viewDidLoad()
_addViews()
_layoutViews()
_setupRxSwift()
}
}
// MARK: - setupRxSwift
extension ViewController {
private func _setupRxSwift() {
// 讓tableView.rx.items屬性監(jiān)聽(tīng)數(shù)據(jù)就可以了讳窟,就這么簡(jiǎn)單,搞定
//
// dataArray是個(gè)Observable
// tableView.rx.items是個(gè)Observer(本質(zhì)就是對(duì)cellForRowAtIndexPath:代理方法的封裝)
dataArray
.bind(to: tableView.rx.items) {
tableView, indexPathRow, data in
var cell = tableView.dequeueReusableCell(withIdentifier: "reuseId")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "reuseId")
}
cell?.textLabel?.text = data
return cell!
}
.disposed(by: bag)
}
}
// MARK: - addViews, layoutViews
extension ViewController {
private func _addViews() {
view.addSubview(tableView)
}
private func _layoutViews() {
tableView.frame = view.bounds
}
}
4??那怎么監(jiān)聽(tīng)cell的點(diǎn)擊呢敞恋?上面我們說(shuō)過(guò)“用戶對(duì)tableView做的操作都是Observable”丽啡,所以很簡(jiǎn)單,拿個(gè)閉包監(jiān)聽(tīng)這個(gè)Observable就行了
// MARK: - setupRxSwift
extension ViewController {
private func _setupRxSwift() {
// 監(jiān)聽(tīng)cell的點(diǎn)擊硬猫,獲取那一行對(duì)應(yīng)的row
tableView.rx.itemSelected
.subscribe(onNext: { indexPath in
print("點(diǎn)擊了cell补箍,對(duì)應(yīng)的row為:\(indexPath.row)")
})
.disposed(by: bag)
// 監(jiān)聽(tīng)cell的點(diǎn)擊改执,獲取那一行對(duì)應(yīng)的data
tableView.rx.modelSelected(String.self)
.subscribe(onNext: { data in
print("點(diǎn)擊了cell,對(duì)應(yīng)的data為:\(data)")
})
.disposed(by: bag)
}
}
5??要想設(shè)置cell的高度坑雅、section header和section header的高度辈挂、section footer和section footer的高度,得通過(guò)Rx提供的方法設(shè)置代理并實(shí)現(xiàn)相關(guān)的代理方法
// MARK: - setupRxSwift
extension ViewController {
private func _setupRxSwift() {
tableView.rx.setDelegate(self).disposed(by: bag)
}
}
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel()
label.text = "我是區(qū)頭"
return label
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let label = UILabel()
label.text = "我是區(qū)尾"
return label
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 44
}
}
6??至于用戶能對(duì)tableView做的其它編輯操作——如插入cell霞丧、刪除cell呢岗、移動(dòng)cell等,你只要知道它們肯定都是Observable就行了蛹尝,找相應(yīng)的API實(shí)現(xiàn)即可后豫。
- 實(shí)現(xiàn)多個(gè)分區(qū)的UITableView
要想實(shí)現(xiàn)多個(gè)分區(qū)的UITableView,必須得安裝RxDataSources
這個(gè)框架突那。
pod 'RxSwift', '~> 5.0'
pod 'RxCocoa', '~> 5.0'
pod 'RxDataSources', '~> 5.0'
然后在想使用的地方導(dǎo)入這個(gè)框架挫酿。
// 這個(gè)框架的本質(zhì)就是使用RxSwift對(duì)UITableView和UICollectionView的數(shù)據(jù)源做了一層包裝,使用它可以大大減少我們的工作量
import RxDataSources
1??同樣地愕难,我們只需要實(shí)現(xiàn)cellForRowAtIndexPath:
這一個(gè)代理方法(是一個(gè)Observer)早龟,讓它來(lái)監(jiān)聽(tīng)數(shù)據(jù)(是一個(gè)Observable)就可以了,RxSwift會(huì)自動(dòng)給我們搞好有多少個(gè)分區(qū)猫缭、每個(gè)分區(qū)里有多少個(gè)cell這些事葱弟,不過(guò)需要注意的是:RxDataSources是專門用來(lái)做多分區(qū)的,所以在給它傳數(shù)據(jù)時(shí)猜丹,dataArray里就不能是普通的數(shù)據(jù)芝加,而必須得是SectionModel或其子類的數(shù)據(jù),這也很好理解射窒,一個(gè)section對(duì)應(yīng)一個(gè)sectionModel嘛藏杖,跟單分區(qū)的UITableView就這個(gè)地方有區(qū)別:構(gòu)建數(shù)據(jù) + 數(shù)據(jù)綁定到tableView。
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class ViewController: UIViewController {
let bag = DisposeBag()
// tableView
lazy var tableView: UITableView = {
let tableView = UITableView(frame: CGRect.zero, style: .plain)
tableView.backgroundColor = UIColor.clear
return tableView
}()
// tableView要顯示的數(shù)據(jù)
//
// 不要定義成普通數(shù)組脉顿,要定義成Observable蝌麸,讓它發(fā)出的事件里掛數(shù)組,這樣才能讓tableView監(jiān)聽(tīng)艾疟,達(dá)到數(shù)據(jù)驅(qū)動(dòng)UI的效果
var dataArray = Observable.just([
// 數(shù)據(jù)這兒有幾個(gè)CustomSectionModel来吩,tableView就會(huì)有幾個(gè)分區(qū)
CustomSectionModel(identityText: "", items: [
// 每個(gè)分區(qū)里的數(shù)據(jù)
"張一",
"張二",
"張三",
]),
CustomSectionModel(identityText: "", items: [
"李一",
"李二",
"李三",
"李四",
]),
CustomSectionModel(identityText: "", items: [
"王一",
"王二",
"王三",
"王四",
"王五",
]),
])
override func viewDidLoad() {
super.viewDidLoad()
_addViews()
_layoutViews()
_setupRxSwift()
}
}
// MARK: - setupRxSwift
extension ViewController {
private func _setupRxSwift() {
// 讓tableView.rx.items(...)方法的返回值監(jiān)聽(tīng)數(shù)據(jù)就可以了,就這么簡(jiǎn)單蔽莱,搞定
//
// dataArray是個(gè)Observable
// tableView.rx.items(...)方法的返回值是個(gè)Observer
dataArray
.bind(to: tableView.rx.items(dataSource: RxTableViewSectionedReloadDataSource<CustomSectionModel>( configureCell: {
(dataSource, tableView, indexPath, data) -> UITableViewCell in
var cell = tableView.dequeueReusableCell(withIdentifier: "reuseId")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "reuseId")
}
cell?.textLabel?.text = data
return cell!
})))
.disposed(by: bag)
tableView.rx.setDelegate(self).disposed(by: bag)
}
}
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel()
label.text = "我是區(qū)頭"
return label
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
}
// MARK: - addViews, layoutViews
extension ViewController {
private func _addViews() {
view.addSubview(tableView)
}
private func _layoutViews() {
tableView.frame = view.bounds
}
}
// MARK: - 自定義SectionModel
struct CustomSectionModel {
// 該分區(qū)的唯一標(biāo)識(shí)误褪,可以定義成任意類型
// 這里我們定義成String類型了,沒(méi)什么特殊需求的話傳個(gè)空字符串""進(jìn)來(lái)就行了
var identityText: String
// 該分區(qū)里的數(shù)據(jù)
// 這里我們分區(qū)里的數(shù)據(jù)都是String類型
var items: [String]
}
extension CustomSectionModel: AnimatableSectionModelType {
var identity: String {
return identityText
}
init(original: CustomSectionModel, items: [String]) {
self = original
self.items = items
}
}
2??其它的實(shí)現(xiàn)跟單分區(qū)的UITableView一樣碾褂。
8兽间、UICollectionView
UICollectionView和UITableView差不多。
二正塌、然后我們寫個(gè)MVVM的小案例
需求很簡(jiǎn)單:
- 用一個(gè)tableView顯示用戶的姓名嘀略、性別恤溶、年齡
- textField輸入“張”時(shí)就請(qǐng)求張姓的用戶顯示,textField輸入“李”時(shí)就請(qǐng)求李姓的用戶顯示
1帜羊、不使用RxSwift時(shí)的MVVM
- Model層:
PersonModel.swift
/*
Model層的數(shù)據(jù)搞成最原始的數(shù)據(jù)即可
*/
import UIKit
class PersonModel: NSObject {
/// 姓名
var name: String?
/// 性別
///
/// 0-未知咒程,1-男,2-女
var sex: Int?
/// 年齡
var age: Int?
init(dict: [String : Any]) {
name = dict["name"] as? String
sex = (dict["sex"] as? NSNumber)?.intValue
age = (dict["age"] as? NSNumber)?.intValue
}
}
- View層:
TableViewCell.swift
/*
View層持有vm讼育,直接拿著數(shù)據(jù)展示即可
*/
import UIKit
class TableViewCell: UITableViewCell {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var sexLabel: UILabel!
@IBOutlet weak var ageLabel: UILabel!
var personVM: PersonViewModel? {
didSet {
guard let personVM = personVM else { return }
nameLabel.text = personVM.name
sexLabel.text = personVM.sex
ageLabel.text = "\(personVM.age)"
}
}
}
- ViewModel層:
PersonViewModel.swift
/*
1帐姻、ViewModel層:負(fù)責(zé)請(qǐng)求數(shù)據(jù)
2、ViewModel層:負(fù)責(zé)處理數(shù)據(jù)
3奶段、ViewModel層:負(fù)責(zé)存儲(chǔ)數(shù)據(jù)
*/
import UIKit
class PersonViewModel {
// 持有一個(gè)_personModel饥瓷,以便處理數(shù)據(jù):VM一對(duì)一Model地添加屬性并處理,搞成計(jì)算屬性即可
private var _personModel: PersonModel?
init(personModel: PersonModel? = nil) {
_personModel = personModel
}
/// vm數(shù)組
///
/// 真正暴露給外面使用的是vm數(shù)組痹籍,里面的數(shù)據(jù)已經(jīng)處理好了呢铆,直接拿著顯示就行了
lazy var personVMArray = [PersonViewModel]()
/// 姓名
var name: String {
return _personModel?.name ?? ""
}
/// 性別
///
/// 0-未知,1-男蹲缠,2-女
var sex: String {
if _personModel?.sex == 1 {
return "男"
} else if _personModel?.sex == 2 {
return "女"
} else {
return "未知"
}
}
/// 年齡
var age: Int {
return _personModel?.age ?? 0
}
}
// MARK: - 請(qǐng)求數(shù)據(jù)
extension PersonViewModel {
/// 請(qǐng)求數(shù)據(jù)
func loadData(params: String, completionHandler: @escaping (_ isSuccess: Bool) -> Void) {
guard let path = Bundle.main.path(forResource: params, ofType: ".plist") else {
completionHandler(false)
return
}
guard let array = NSArray(contentsOfFile: path) as? [[String : Any]] else {
completionHandler(false)
return
}
for dict in array {
let personModel = PersonModel(dict: dict)
let personVM = PersonViewModel(personModel: personModel)
personVMArray.append(personVM)
completionHandler(true)
}
}
}
- Controller層:
ViewController.swift
/*
1棺克、Controller層:持有view,創(chuàng)建并把view添加到界面上
2线定、Controller層:持有vm娜谊,調(diào)用vm的方法請(qǐng)求數(shù)據(jù)
3、vm --> view:Controller調(diào)用vm的方法請(qǐng)求數(shù)據(jù)斤讥,請(qǐng)求完成后vm是通過(guò)block的方式告訴Controller的纱皆,Controller可以調(diào)用一下view的reloadData方法把vm里最新存儲(chǔ)的數(shù)據(jù)賦值給view顯示
4、view --> vm:view產(chǎn)生的變化是通過(guò)代理告訴Controller的周偎,Controller可以調(diào)用vm的方法把view發(fā)生的變化告訴它
*/
import UIKit
class ViewController: UIViewController {
private lazy var _textField: UITextField = {
let textField = UITextField()
textField.backgroundColor = UIColor.red
textField.returnKeyType = .done
textField.delegate = self
return textField
}()
private lazy var _tableView: UITableView = {
let tableView = UITableView()
tableView.backgroundColor = UIColor.green
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "cell")
tableView.dataSource = self
tableView.delegate = self
return tableView
}()
private lazy var _personVM = PersonViewModel()
private var _insertText = "張";
override func viewDidLoad() {
super.viewDidLoad()
_addViews()
_layoutViews()
_loadData(params: _insertText)
}
}
// MARK: - UITextFieldDelegate
extension ViewController: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
_insertText = textField.text ?? ""
_personVM.loadData(params: _insertText) { isSuccess in
if isSuccess {
self._tableView.reloadData()
} else {
print("請(qǐng)求數(shù)據(jù)出錯(cuò)")
}
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
view.endEditing(true)
return true
}
}
// MARK: - UITableViewDataSource, UITableViewDelegate
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return _personVM.personVMArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.personVM = _personVM.personVMArray[indexPath.row];
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
// MARK: - 請(qǐng)求數(shù)據(jù)
extension ViewController {
private func _loadData(params: String) {
_personVM.loadData(params: params) { isSuccess in
if isSuccess {
self._tableView.reloadData()
} else {
print("請(qǐng)求數(shù)據(jù)出錯(cuò)")
}
}
}
}
// MARK: - setupUI
extension ViewController {
private func _addViews() {
view.addSubview(_textField)
view.addSubview(_tableView)
}
private func _layoutViews() {
_textField.frame = CGRect(x: 0, y: 20, width: view.bounds.size.width, height: 44)
_tableView.frame = CGRect(x: 0, y: 64, width: view.bounds.size.width, height: view.bounds.size.height - 64)
}
}
2、使用RxSwift時(shí)的MVVM
Model層和View層都不需要改動(dòng)
ViewModel層:
PersonViewModel.swift
/*
1撑帖、ViewModel層:負(fù)責(zé)請(qǐng)求數(shù)據(jù)
2蓉坎、ViewModel層:負(fù)責(zé)處理數(shù)據(jù)
3、ViewModel層:負(fù)責(zé)存儲(chǔ)數(shù)據(jù)
*/
import UIKit
import RxSwift
class PersonViewModel {
// 持有一個(gè)_personModel胡嘿,以便處理數(shù)據(jù):VM一對(duì)一Model地添加屬性并處理蛉艾,搞成計(jì)算屬性即可
private var _personModel: PersonModel?
init(personModel: PersonModel? = nil) {
_personModel = personModel
}
//-----------變化1-----------//
/// 同時(shí)新增一個(gè)跟原來(lái)同名的vm數(shù)組,使用RxSwift:
/// 1衷敌、因?yàn)橥饷鎡ableView要顯示的數(shù)據(jù)就是這個(gè)數(shù)組里的數(shù)據(jù)勿侯,換句話說(shuō)tableView要監(jiān)聽(tīng)這個(gè)數(shù)組,所以這個(gè)數(shù)組就不能再定義成普通的數(shù)據(jù)了缴罗,而應(yīng)該定義成一個(gè)Observable助琐,里面的事件掛的數(shù)據(jù)是數(shù)組
/// 2、又因?yàn)檫@個(gè)數(shù)組里的數(shù)據(jù)會(huì)隨著textField輸入文本的變化而變化面氓,換句話說(shuō)這個(gè)數(shù)組應(yīng)該監(jiān)聽(tīng)textField的文本變化兵钮,所以這個(gè)數(shù)組應(yīng)該定義成一個(gè)Observer
/// 3蛆橡、所以最終我們得把這個(gè)數(shù)組定義成一個(gè)Subjects
var personVMArray = PublishSubject<[PersonViewModel]>()
/// 我們把原來(lái)的personVMArray直接降級(jí)成一個(gè)私有屬性,繼續(xù)搞它原來(lái)負(fù)責(zé)的事情
private lazy var _personVMArray = [PersonViewModel]()
//-----------變化1-----------//
/// 姓名
var name: String {
return _personModel?.name ?? ""
}
/// 性別
///
/// 0-未知掘譬,1-男泰演,2-女
var sex: String {
if _personModel?.sex == 1 {
return "男"
} else if _personModel?.sex == 2 {
return "女"
} else {
return "未知"
}
}
/// 年齡
var age: Int {
return _personModel?.age ?? 0
}
}
// MARK: - 請(qǐng)求數(shù)據(jù)
extension PersonViewModel {
/// 請(qǐng)求數(shù)據(jù)
func loadData(params: String, completionHandler: @escaping (_ isSuccess: Bool) -> Void) {
guard let path = Bundle.main.path(forResource: params, ofType: ".plist") else {
completionHandler(false)
return
}
guard let array = NSArray(contentsOfFile: path) as? [[String : Any]] else {
completionHandler(false)
return
}
_personVMArray.removeAll()
for dict in array {
let personModel = PersonModel(dict: dict)
let personVM = PersonViewModel(personModel: personModel)
_personVMArray.append(personVM)
}
completionHandler(true)
//-----------變化2-----------//
// 請(qǐng)求成功后,Observable發(fā)出一個(gè)next事件把數(shù)據(jù)發(fā)出去
personVMArray.onNext(_personVMArray)
//-----------變化2-----------//
}
}
- Controller層:
ViewController.swift
/*
1葱轩、Controller層:持有view睦焕,創(chuàng)建并把view添加到界面上
2、Controller層:持有vm靴拱,調(diào)用vm的方法請(qǐng)求數(shù)據(jù)
3垃喊、vm --> view:Controller調(diào)用vm的方法請(qǐng)求數(shù)據(jù),請(qǐng)求完成后vm是通過(guò)block的方式告訴Controller的缭嫡,但是Controller只需要處理出錯(cuò)的情況缔御,成功的情況什么都不需要做,因?yàn)閿?shù)據(jù)已經(jīng)自動(dòng)驅(qū)動(dòng)UI了——數(shù)據(jù)已經(jīng)綁定到tableView上了
4妇蛀、view --> vm:view產(chǎn)生的變化不再是通過(guò)代理而是通過(guò)Rx的鏈?zhǔn)秸{(diào)用告訴Controller的耕突,Controller可以調(diào)用vm的方法把view發(fā)生的變化告訴它
*/
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let bag = DisposeBag()
private lazy var _textField: UITextField = {
let textField = UITextField()
textField.backgroundColor = UIColor.red
textField.returnKeyType = .done
return textField
}()
private lazy var _tableView: UITableView = {
let tableView = UITableView()
tableView.backgroundColor = UIColor.green
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "cell")
return tableView
}()
private lazy var _personVM = PersonViewModel()
private var _insertText = "張";
override func viewDidLoad() {
super.viewDidLoad()
// 數(shù)據(jù)已經(jīng)在VM里搞定了,所以這里先搞定UI
_addViews()
_layoutViews()
// 再把數(shù)據(jù)和UI進(jìn)行雙向綁定
_setupRxSwift()
// 初始請(qǐng)求數(shù)據(jù)
_loadData(params: _insertText)
}
}
// MARK: - setupRxSwift
extension ViewController {
private func _setupRxSwift() {
// 數(shù)據(jù)綁定到tableView上:數(shù)據(jù)驅(qū)動(dòng)UI
_personVM.personVMArray
.bind(to: _tableView.rx.items) {
tableView, indexPathRow, data in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: IndexPath(row: indexPathRow, section: 0)) as! TableViewCell
cell.personVM = data
return cell
}
.disposed(by: bag)
_tableView.rx.setDelegate(self).disposed(by: bag)
// textField的內(nèi)容發(fā)生變化后评架,重新請(qǐng)求數(shù)據(jù):UI驅(qū)動(dòng)數(shù)據(jù)
_textField.rx.controlEvent(.editingDidEnd)
.subscribe { _ in
// textField結(jié)束編輯了眷茁、光標(biāo)停止閃動(dòng)
self._insertText = self._textField.text ?? ""
self._personVM.loadData(params: self._insertText) { isSuccess in
if !isSuccess {
print("請(qǐng)求數(shù)據(jù)出錯(cuò)")
}
}
}
.disposed(by: bag)
_textField.rx.controlEvent(.editingDidEndOnExit)
.subscribe { _ in
// 點(diǎn)擊了鍵盤上的return鍵結(jié)束編輯,緊接著會(huì)觸發(fā)【textField結(jié)束編輯了纵诞、光標(biāo)停止閃動(dòng)】
print("textFieldShouldReturn")
}
.disposed(by: bag)
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
// MARK: - 請(qǐng)求數(shù)據(jù)
extension ViewController {
private func _loadData(params: String) {
_personVM.loadData(params: params) { isSuccess in
if !isSuccess {
print("請(qǐng)求數(shù)據(jù)出錯(cuò)")
}
}
}
}
// MARK: - setupUI
extension ViewController {
private func _addViews() {
view.addSubview(_textField)
view.addSubview(_tableView)
}
private func _layoutViews() {
_textField.frame = CGRect(x: 0, y: 20, width: view.bounds.size.width, height: 44)
_tableView.frame = CGRect(x: 0, y: 64, width: view.bounds.size.width, height: view.bounds.size.height - 64)
}
}
參考
1上祈、RxSwift中文文檔
2、RxSwift大全