学习RxSwift/RxCocoa的一些简单用法

作为一个iOS开发的小学生,看靛青KRxSwift 系列教程,才到开头的002节就让我一头雾水了,发现自己的学习能力很废,于是就看看官方repo的Example,简单粗暴地把一些最直接的使用方法记录下来,先不求甚解,囫囵吞枣,待日后哪天被雷劈到了灵光乍现,再来深入研究。

话说Rx里面的Playground特别好玩,能把教程做成这个样子,真的是Wonderful啊!
如果你完全不知道RxSwift是什么的话,这里有个很好的实例
如果你对基本的序列处理不太清楚的话,这里有很棒的文章,配合官方的Playground,保证能看懂。
Raywenderlich的这篇小白入门教程也很好,这里有翻译版

基本UI控件的扩展

UITextField / UITextView

  • 监听三个UITextField随便哪个的text变化了,都会引起代码执行,并且结果赋值到resulttext
@IBOutlet weak var number1: UITextField!
@IBOutlet weak var number2: UITextField!
@IBOutlet weak var number3: UITextField!

@IBOutlet weak var result: UILabel!

var disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()

Observable.combineLatest(number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty) { textValue1, textValue2, textValue3 -> Int in
return (Int(textValue1) ?? 0) + (Int(textValue2) ?? 0) + (Int(textValue3) ?? 0)
}
.map { $0.description }
.bindTo(result.rx.text)
.disposed(by: disposeBag)
}
  • 单个UITextField变化监听

    官方:

    // also test two way binding
    let textValue = Variable("")
    _ = textField.rx.textInput <-> textValue

    textValue.asObservable()
    .subscribe(onNext: { [weak self] x in
    self?.debug("UITextField text \(x)")
    })
    .disposed(by: disposeBag)

    例子:

    /// 取手机号后6位作为初始密码,不足11位就空串
    func autoGeneratePassword(){
    _ = usernameTF.rx.text.orEmpty.asObservable()
    .distinctUntilChanged()
    .map{
    if $0.characters.count == 11 {
    return $0.substring(from: $0.index($0.startIndex, offsetBy: 5))
    }else{
    return ""
    }
    }
    .bindTo(passwordTF.rx.text)
    .disposed(by: disposeBag)
    }
  • 单个UITextField事件监听

    usernameTF.rx.controlEvent([.editingDidBegin]) //状态可以组合
    .asObservable()
    .subscribe(onNext: { _ in
    print("begin edit")
    }).disposed(by: disposeBag)
  • UITextField遇到键盘Next/Go按钮时,焦点的切换

    这里有个小坑,如果usernameTFpasswordTF用了Observable.combineLatest然后订阅的话,你会发现下面的delegate代码没用了,在usernameTF点击Next/Go不能切换到passwordTF了:

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    if textField == usernameTF {
    passwordTF.becomeFirstResponder()
    } else {
    passwordTF.resignFirstResponder()
    Log("view become")
    }
    return true
    }

    用事件监听即可,监听return key结束编辑的事件(editingDidEndOnExit):

    /* editingDidEndOnExit: UIControlEvents // 'return key' ending editing */

    usernameTF.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: { [weak self] (_) in
    self?.passwordTF.becomeFirstResponder()
    }).disposed(by: disposeBag)

    passwordTF.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: { [weak self] (_) in
    self?.passwordTF.resignFirstResponder()
    }).disposed(by: disposeBag)
  • UITextView变化监听

    // also test two way binding
    let textViewValue = Variable("")
    _ = textView.rx.textInput <-> textViewValue

    textViewValue.asObservable()
    .subscribe(onNext: { [weak self] x in
    self?.debug("UITextView text \(x)")
    })
    .disposed(by: disposeBag)

UIButton / UIBarButtonItem

  • 监听按钮的点击(touchUpInside

    @IBOutlet weak var doSomethingOutlet: UIButton!

    doSomethingOutlet.rx.tap
    .subscribe(onNext: { [weak self] in
    self?.showAlert() //这里self指vc
    })
    .disposed(by: disposeBag)

    也可以这样:

    button.rx.tap
    .bindNext { [weak self] in
    self?.openAppPreferences()
    }
    .disposed(by: disposeBag)

UISegmentedControl / UISwitch

  • 监听状态切换

    // also test two way binding
    let segmentedValue = Variable(0)
    _ = segmentedControl.rx.value <-> segmentedValue

    segmentedValue.asObservable()
    .subscribe(onNext: { [weak self] x in
    self?.debug("UISegmentedControl value \(x)")
    })
    .disposed(by: disposeBag)


    // also test two way binding
    let switchValue = Variable(true)
    _ = switcher.rx.value <-> switchValue

    switchValue.asObservable()
    .subscribe(onNext: { [weak self] x in
    self?.debug("UISwitch value \(x)")
    })
    .disposed(by: disposeBag)

UIActivityIndicatorView

  • 状态指示器根据UISwitch的状态来决定是否显示旋转

    switcher.rx.value
    .bindTo(activityIndicator.rx.isAnimating)
    .disposed(by: disposeBag)

UISlider

// also test two way binding
let sliderValue = Variable<Float>(1.0)
_ = slider.rx.value <-> sliderValue

sliderValue.asObservable()
.subscribe(onNext: { [weak self] x in
self?.debug("UISlider value \(x)")
})
.disposed(by: disposeBag)

UIDatePicker

// also test two way binding
let dateValue = Variable(Date(timeIntervalSince1970: 0))
_ = datePicker.rx.date <-> dateValue

dateValue.asObservable()
.subscribe(onNext: { [weak self] x in
self?.debug("UIDatePicker date \(x)")
})
.disposed(by: disposeBag)

UIGestureRecognizer

手势回调,xUIPanGestureRecognizer对象

mypan.rx.event
.subscribe(onNext: { [weak self] x in
self?.debug("UIGestureRecognizer event \(x.state)")
let p = x.location(in: x.view)
self?.debug("pan ges point: \(p)")
})
.disposed(by: disposeBag)

CLLocationManager

还有一堆didxxx方法,自己Command点进去看

manager.requestWhenInUseAuthorization()

manager.rx.didUpdateLocations
.subscribe(onNext: { x in
print("rx.didUpdateLocations \(x)")
})
.disposed(by: disposeBag)

_ = manager.rx.didFailWithError
.subscribe(onNext: { x in
print("rx.didFailWithError \(x)")
})

manager.rx.didChangeAuthorizationStatus
.subscribe(onNext: { status in
print("Authorization status \(status)")
})
.disposed(by: disposeBag)

manager.startUpdatingLocation()

UITableView的扩展

单个section的情况

除了下面几个最常用的,还有itemDeselecteditemInserteditemDeleted等等行为可以订阅,自己Command点进去看

class SimpleTableViewExampleViewController : ViewController, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!

override func viewDidLoad() {
super.viewDidLoad()

let items = Observable.just(
(0..<20).map { "\($0)" }
)

//大概是对cellForRowAtIndexPath的封装
items
.bindTo(tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) {
(row, element, cell) in
//element 是 items[row]元素
cell.textLabel?.text = "\(element) @ row \(row)"
}
.disposed(by: disposeBag)


//点击某个cell触发, value 是 items[row],类似的是tableView.rx.itemSelected,都是对didSelectRowAtIndexPath的封装
tableView.rx
.modelSelected(String.self)
.subscribe(onNext: { value in
DefaultWireframe.presentAlert("Tapped `\(value)`")
})
.disposed(by: disposeBag)

//accessoryButtonTappedForRowWithIndexPath
tableView.rx
.itemAccessoryButtonTapped
.subscribe(onNext: { indexPath in
DefaultWireframe.presentAlert("Tapped Detail @ \(indexPath.section),\(indexPath.row)")
})
.disposed(by: disposeBag)

}

}

多section的情况

class SimpleTableViewExampleSectionedViewController : ViewController, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!

let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Double>>()

override func viewDidLoad() {
super.viewDidLoad()

let dataSource = self.dataSource

let items = Observable.just([
SectionModel(model: "First section", items: [
11.0,
12.0,
13.0
]),
SectionModel(model: "Second section", items: [
21.0,
22.0,
23.0
]),
SectionModel(model: "Third section", items: [
31.0,
32.0,
33.0
]),
])

dataSource.configureCell = { (_, tv, indexPath, element) in
let cell = tv.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = "\(element) @ row \(indexPath.row)"
return cell
}

items
.bindTo(tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)

tableView.rx
.itemSelected
.map { indexPath in
return (indexPath, dataSource[indexPath])
}
.subscribe(onNext: { indexPath, model in
DefaultWireframe.presentAlert("Tapped `\(model)` @ \(indexPath)")
})
.disposed(by: disposeBag)

tableView.rx
.setDelegate(self)
.disposed(by: disposeBag)
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel(frame: CGRect.zero)
label.text = dataSource[section].model
return label
}

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30
}

// to prevent swipe to delete behavior
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
}