【RxSwift】RxSwift在MVVM方面的實(shí)際應(yīng)用

目錄
一、首先我們看看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.backgroundColorrx.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.didEndDraggingrx.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.willBeginDraggingrx.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大全

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浙芙,一起剝皮案震驚了整個(gè)濱河市登刺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗡呼,老刑警劉巖纸俭,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異南窗,居然都是意外死亡揍很,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門万伤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窒悔,“玉大人,你說(shuō)我怎么就攤上這事敌买〖蛑椋” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵虹钮,是天一觀的道長(zhǎng)北救。 經(jīng)常有香客問(wèn)我荐操,道長(zhǎng),這世上最難降的妖魔是什么珍策? 我笑而不...
    開(kāi)封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任托启,我火速辦了婚禮,結(jié)果婚禮上攘宙,老公的妹妹穿的比我還像新娘屯耸。我一直安慰自己,他們只是感情好蹭劈,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布疗绣。 她就那樣靜靜地躺著,像睡著了一般铺韧。 火紅的嫁衣襯著肌膚如雪多矮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天哈打,我揣著相機(jī)與錄音塔逃,去河邊找鬼。 笑死料仗,一個(gè)胖子當(dāng)著我的面吹牛湾盗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播立轧,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼格粪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了氛改?” 一聲冷哼從身側(cè)響起帐萎,我...
    開(kāi)封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胜卤,沒(méi)想到半個(gè)月后疆导,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瑰艘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年是鬼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肤舞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片紫新。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖李剖,靈堂內(nèi)的尸體忽然破棺而出芒率,到底是詐尸還是另有隱情,我是刑警寧澤篙顺,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布偶芍,位于F島的核電站充择,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏匪蟀。R本人自食惡果不足惜椎麦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望材彪。 院中可真熱鬧观挎,春花似錦、人聲如沸段化。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)显熏。三九已至雄嚣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喘蟆,已是汗流浹背缓升。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留履肃,地道東北人仔沿。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像尺棋,于是被迫代替她去往敵國(guó)和親封锉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容