@TOC
Rxswift 常用的数据处理
Target Action
实例1:
- 传统代码
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
func buttonTapped() {
print("button Tapped")
}
- Rxswift代码
button.rx.tap
.subscribe(onNext: {
print("button Tapped")
})
.disposed(by: disposeBag)
你不需要使用 Target Action,这样使得代码逻辑清晰可见。
代理
实例2:
- 传统代码
class ViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("contentOffset: \(scrollView.contentOffset)")
}
}
- Rxswift代码
class ViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
scrollView.rx.contentOffset
.subscribe(onNext: { contentOffset in
print("contentOffset: \(contentOffset)")
})
.disposed(by: disposeBag)
}
}
Rxswift实现的代理,你不需要书写代理的配置代码,就能获得想要的结果。
通知
实例3:
- 传统代码
var ntfObserver: NSObjectProtocol!
override func viewDidLoad() {
super.viewDidLoad()
ntfObserver = NotificationCenter.default.addObserver(
forName: .UIApplicationWillEnterForeground,
object: nil, queue: nil) { (notification) in
print("Application Will Enter Foreground")
}
}
deinit {
NotificationCenter.default.removeObserver(ntfObserver)
}
- Rxswift代码
NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
.subscribe(onNext: { (noti) in
print(noti)
})
.disposed(by: disposeBag)
闭包回调
实例4:
- 传统代码
URLSession.shared.dataTask(with: URLRequest(url: url)) {
(data, response, error) in
guard error == nil else {
print("Data Task Error: \(error!)")
return
}
guard let data = data else {
print("Data Task Error: unknown")
return
}
print("Data Task Success with count: \(data.count)")
}.resume()
- Rxswift代码
URLSession.shared.rx.data(request: URLRequest(url: url))
.subscribe(onNext: { data in
print("Data Task Success with count: \(data.count)")
}, onError: { error in
print("Data Task Error: \(error)")
})
.disposed(by: disposeBag)
KVO
实例5:
- 传统代码
- Rxswift代码
//监听person对象的name的变化
self.person.rx.observeWeakly(String.self, "name")
.subscribe(onNext: { (value) in
print(value as Any)
})
.disposed(by: disposeBag)
手势
实例6:
- 传统代码
- Rxswift代码
let disposeBag = DisposeBag()
let tap = UITapGestureRecognizer()
self.label.addGestureRecognizer(tap)
self.label.isUserInteractionEnabled = true
tap.rx.event.subscribe(onNext: { (tap) in
print(tap.view)
})
.disposed(by: disposeBag)
网路请求
实例7:
- 传统代码
- Rxswift代码
let url = URL(string: "https://www.baidu.com")
URLSession.shared.rx.response(request: URLRequest(url: url!)).subscribe(onNext: { (response,data) in
print(response)
}, onError: { (error) in
print(error)
}, onCompleted: {
}).disposed(by: disposeBag)
定时器
实例8:
- 传统代码
- Rxswift代码
let disposeBag = DisposeBag()
var timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
timer.subscribe(onNext: { (num) in
print(num)
})
.disposed(by: disposeBag)
多个任务之间有依赖关系
实例9:
例如,先通过用户名密码取得 Token 然后通过 Token 取得用户信息
- 传统代码
/// 用回调的方式封装接口
enum API {
/// 通过用户名密码取得一个 token
static func token(username: String, password: String,
success: (String) -> Void,
failure: (Error) -> Void) { ... }
/// 通过 token 取得用户信息
static func userinfo(token: String,
success: (UserInfo) -> Void,
failure: (Error) -> Void) { ... }
}
/// 通过用户名和密码获取用户信息
API.token(username: "beeth0ven", password: "987654321",
success: { token in
API.userInfo(token: token,
success: { userInfo in
print("获取用户信息成功: \(userInfo)")
},
failure: { error in
print("获取用户信息失败: \(error)")
})
},
failure: { error in
print("获取用户信息失败: \(error)")
})
- Rxswift代码
/// 用 Rx 封装接口
enum API {
/// 通过用户名密码取得一个 token
static func token(username: String, password: String) -> Observable<String> { ... }
/// 通过 token 取得用户信息
static func userInfo(token: String) -> Observable<UserInfo> { ... }
}
/// 通过用户名和密码获取用户信息
API.token(username: "beeth0ven", password: "987654321")
.flatMapLatest(API.userInfo)
.subscribe(onNext: { userInfo in
print("获取用户信息成功: \(userInfo)")
}, onError: { error in
print("获取用户信息失败: \(error)")
})
.disposed(by: disposeBag)
等待多个并发任务完成后处理结果
实例10:
例如,需要将两个网络请求合并成一个
通过 Rx 来实现:
/// 用 Rx 封装接口
enum API {
/// 取得老师的详细信息
static func teacher(teacherId: Int) -> Observable<Teacher> { ... }
/// 取得老师的评论
static func teacherComments(teacherId: Int) -> Observable<[Comment]> { ... }
}
/// 同时取得老师信息和老师评论
Observable.zip(
API.teacher(teacherId: teacherId),
API.teacherComments(teacherId: teacherId)
).subscribe(onNext: { (teacher, comments) in
print("获取老师信息成功: \(teacher)")
print("获取老师评论成功: \(comments.count) 条")
}, onError: { error in
print("获取老师信息或评论失败: \(error)")
})
.disposed(by: disposeBag)
这样你可用寥寥几行代码来完成相当复杂的异步操作。
数据绑定
实例11:
在 RxSwift 里有一个比较重要的概念就是数据绑定(订阅)。就是指将可监听序列绑定到观察者上:
我们对比一下这两段代码:
- 传统代码:
将一个单独的图片设置到imageView上
let image: UIImage = UIImage(named: ...)
imageView.image = image
- Rx代码:
let image: Observable<UIImage> = ...
image.bind(to: imageView.rx.image)
Rx代码:上面这段代码是将一个图片序列 “同步” 到imageView上。这个序列里面的图片可以是异步产生的。这里定义的 image 就是上图中蓝色部分(可监听序列),imageView.rx.image就是上图中橙色部分(观察者)。而这种 “同步机制” 就是数据绑定(订阅)。
Rxswift UI 用法
UILabel
实例30:
- 传统代码
- Rxswift代码
- Rxswift简单使用UILabel
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//创建文本标签
let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))
self.view.addSubview(label)
//创建一个计时器(每0.1秒发送一个索引数)
let timer = Observable<Int>.interval(0.1, scheduler: MainScheduler.instance)
//将已过去的时间格式化成想要的字符串,并绑定到label上
timer.map{ String(format: "%0.2d:%0.2d.%0.1d",
arguments: [($0 / 600) % 600, ($0 % 600 ) / 10, $0 % 10]) }
.bind(to: label.rx.text)
.disposed(by: disposeBag)
}
}
- UILabel富文本 :将数据绑定到 attributedText 属性上
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//创建文本标签
let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))
self.view.addSubview(label)
//创建一个计时器(每0.1秒发送一个索引数)
let timer = Observable<Int>.interval(0.1, scheduler: MainScheduler.instance)
//将已过去的时间格式化成想要的字符串,并绑定到label上
timer.map(formatTimeInterval)
.bind(to: label.rx.attributedText)
.disposed(by: disposeBag)
}
//将数字转成对应的富文本
func formatTimeInterval(ms: NSInteger) -> NSMutableAttributedString {
let string = String(format: "%0.2d:%0.2d.%0.1d",
arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])
//富文本设置
let attributeString = NSMutableAttributedString(string: string)
//从文本0开始6个字符字体HelveticaNeue-Bold,16号
attributeString.addAttribute(NSAttributedStringKey.font,
value: UIFont(name: "HelveticaNeue-Bold", size: 16)!,
range: NSMakeRange(0, 5))
//设置字体颜色
attributeString.addAttribute(NSAttributedStringKey.foregroundColor,
value: UIColor.white, range: NSMakeRange(0, 5))
//设置文字背景颜色
attributeString.addAttribute(NSAttributedStringKey.backgroundColor,
value: UIColor.orange, range: NSMakeRange(0, 5))
return attributeString
}
}
UIButton
实例40:
- 传统代码
self.button.addTarget(self, action:#selector(buttonTapped(sender:)), for: UIControlEvents.touchUpInside)
@objc func buttonTapped(sender:UIButton?){
}
- Rxswift代码
let disposeBag = DisposeBag()
//由于tap事件里点击事件用的最多,所以RX默认的tap就是点击事件
self.button.rx.tap
.subscribe(onNext: { () in
print("点击来了")
})
.disposed(by: disposeBag)
//RXSwift监听按钮除了点击外的事件:
self.button.rx.controlEvent(.touchUpOutside).subscribe(onNext: { () in
})
.disposed(by: disposeBag)
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
@IBOutlet weak var button: UIButton!
override func viewDidLoad() {
//按钮点击响应1
button.rx.tap
.subscribe(onNext: { [weak self] in
self?.showMessage("按钮被点击")
})
.disposed(by: disposeBag)
//按钮点击响应2
button.rx.tap
.bind { [weak self] in
self?.showMessage("按钮被点击")
}
.disposed(by: disposeBag)
//
}
//按钮标题(title)的绑定
func test1() {
//创建一个计时器(每1秒发送一个索引数)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
//根据索引数拼接最新的标题,并绑定到button上
timer.map{"计数\($0)"}
.bind(to: button.rx.title(for: .normal))
.disposed(by: disposeBag)
}
//按钮富文本标题(attributedTitle)的绑定
func test2() {
//创建一个计时器(每1秒发送一个索引数)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
//将已过去的时间格式化成想要的字符串,并绑定到button上
timer.map(formatTimeInterval)
.bind(to: button.rx.attributedTitle())
.disposed(by: disposeBag)
}
//按钮图标(image)的绑定
func test3() {
//创建一个计时器(每1秒发送一个索引数)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
//根据索引数选择对应的按钮图标,并绑定到button上
timer.map({
let name = $0%2 == 0 ? "back" : "forward"
return UIImage(named: name)!
})
.bind(to: button.rx.image())
.disposed(by: disposeBag)
}
//按钮背景图片(backgroundImage)的绑定
func test4() {
//创建一个计时器(每1秒发送一个索引数)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
//根据索引数选择对应的按钮背景图,并绑定到button上
timer.map{ UIImage(named: "\($0%2)")! }
.bind(to: button.rx.backgroundImage())
.disposed(by: disposeBag)
}
//将数字转成对应的富文本
func formatTimeInterval(ms: NSInteger) -> NSMutableAttributedString {
let string = String(format: "%0.2d:%0.2d.%0.1d",
arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])
//富文本设置
let attributeString = NSMutableAttributedString(string: string)
//从文本0开始6个字符字体HelveticaNeue-Bold,16号
attributeString.addAttribute(NSAttributedStringKey.font,
value: UIFont(name: "HelveticaNeue-Bold", size: 16)!,
range: NSMakeRange(0, 5))
//设置字体颜色
attributeString.addAttribute(NSAttributedStringKey.foregroundColor,
value: UIColor.white, range: NSMakeRange(0, 5))
//设置文字背景颜色
attributeString.addAttribute(NSAttributedStringKey.backgroundColor,
value: UIColor.orange, range: NSMakeRange(0, 5))
return attributeString
}
//显示消息提示框
func showMessage(_ text: String) {
let alertController = UIAlertController(title: text, message: nil, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "确定", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
}
UIBarButtonItem
实例50:
- 传统代码
- Rxswift代码
UISwitch
实例60:
- 传统代码
- Rxswift代码
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
//分段选择控件
@IBOutlet weak var segmented: UISegmentedControl!
//图片显示控件
@IBOutlet weak var imageView: UIImageView!
let disposeBag = DisposeBag()
override func viewDidLoad() {
//创建一个当前需要显示的图片的可观察序列
let showImageObservable: Observable<UIImage> =
segmented.rx.selectedSegmentIndex.asObservable().map {
let images = ["js.png", "php.png", "react.png"]
return UIImage(named: images[$0])!
}
//把需要显示的图片绑定到 imageView 上
showImageObservable.bind(to: imageView.rx.image)
.disposed(by: disposeBag)
}
func test1() {
switch1.rx.isOn.asObservable()
.subscribe(onNext: {
print("当前开关状态:\($0)")
})
.disposed(by: disposeBag)
}
func test2() {
switch1.rx.isOn
.bind(to: button1.rx.isEnabled)
.disposed(by: disposeBag)
}
}
UISegmentedControl
实例70:
- 传统代码
- Rxswift代码
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
//分段选择控件
@IBOutlet weak var segmented: UISegmentedControl!
//图片显示控件
@IBOutlet weak var imageView: UIImageView!
let disposeBag = DisposeBag()
override func viewDidLoad() {
//创建一个当前需要显示的图片的可观察序列
let showImageObservable: Observable<UIImage> =
segmented.rx.selectedSegmentIndex.asObservable().map {
let images = ["js.png", "php.png", "react.png"]
return UIImage(named: images[$0])!
}
//把需要显示的图片绑定到 imageView 上
showImageObservable.bind(to: imageView.rx.image)
.disposed(by: disposeBag)
}
func test1() {
segmented.rx.selectedSegmentIndex.asObservable()
.subscribe(onNext: {
print("当前项:\($0)")
})
.disposed(by: disposeBag)
}
}
UIActivityIndicatorView
实例80:
- 传统代码
- Rxswift代码
UITextField
实例90:UITextField使用Rxswift的基本用法
- 传统代码
- Rxswift代码
self.textFiled.rx.text.orEmpty
.subscribe(onNext: { (text) in
print(text)
})
.disposed(by: disposeBag)
// textfiled绑定Button的文字
self.textFiled.rx.text
.bind(to: self.button.rx.title())
.disposed(by: disposeBag)
实例91:Rxswift监听单个 textField 内容的变化
- Rxswift代码
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//创建文本输入框
let textField = UITextField(frame: CGRect(x:10, y:80, width:200, height:30))
textField.borderStyle = UITextBorderStyle.roundedRect
self.view.addSubview(textField)
//当文本框内容改变时,将内容输出到控制台上
textField.rx.text.orEmpty.asObservable()
.subscribe(onNext: {
print("您输入的是:\($0)")
})
.disposed(by: disposeBag)
//当文本框内容改变时,将内容输出到控制台上
textField.rx.text.orEmpty.changed
.subscribe(onNext: {
print("您输入的是:\($0)")
})
.disposed(by: disposeBag)
}
}
实例92:Rxswift将textField的内容绑定到其他控件上
- Rxswift代码
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//创建文本输入框
let inputField = UITextField(frame: CGRect(x:10, y:80, width:200, height:30))
inputField.borderStyle = UITextBorderStyle.roundedRect
self.view.addSubview(inputField)
//创建文本输出框
let outputField = UITextField(frame: CGRect(x:10, y:150, width:200, height:30))
outputField.borderStyle = UITextBorderStyle.roundedRect
self.view.addSubview(outputField)
//创建文本标签
let label = UILabel(frame:CGRect(x:20, y:190, width:300, height:30))
self.view.addSubview(label)
//创建按钮
let button:UIButton = UIButton(type:.system)
button.frame = CGRect(x:20, y:230, width:40, height:30)
button.setTitle("提交", for:.normal)
self.view.addSubview(button)
//当文本框内容改变
let input = inputField.rx.text.orEmpty.asDriver() // 将普通序列转换为 Driver
.throttle(0.3) //在主线程中操作,0.3秒内值若多次改变,取最后一次
//内容绑定到另一个输入框中
input.drive(outputField.rx.text)
.disposed(by: disposeBag)
//内容绑定到文本标签中
input.map{ "当前字数:\($0.count)" }
.drive(label.rx.text)
.disposed(by: disposeBag)
//根据内容字数决定按钮是否可用
input.map{ $0.count > 5 }
.drive(button.rx.isEnabled)
.disposed(by: disposeBag)
}
}
实例93:Rxswift同时监听多个 textField 内容的变化
- Rxswift代码
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
@IBOutlet weak var textField1: UITextField!
@IBOutlet weak var textField2: UITextField!
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
Observable.combineLatest(textField1.rx.text.orEmpty, textField2.rx.text.orEmpty) {
textValue1, textValue2 -> String in
return "你输入的号码是:\(textValue1)-\(textValue2)"
}
.map { $0 }
.bind(to: label.rx.text)
.disposed(by: disposeBag)
}
}
实例94:Rxswift实现textField事件监听
通过 rx.controlEvent 可以监听输入框的各种事件,且多个事件状态可以自由组合。除了各种 UI 控件都有的 touch 事件外,输入框还有如下几个独有的事件:
-
editingDidBegin:开始编辑(开始输入内容)
-
editingChanged:输入内容发生改变
-
editingDidEnd:结束编辑
-
editingDidEndOnExit:按下 return 键结束编辑
-
allEditingEvents:包含前面的所有编辑相关事件
- Rxswift代码
textField.rx.controlEvent([.editingDidBegin]) //状态可以组合
.asObservable()
.subscribe(onNext: { _ in
print("开始编辑内容!")
}).disposed(by: disposeBag)
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
//用户名输入框
@IBOutlet weak var username: UITextField!
//密码输入框
@IBOutlet weak var password: UITextField!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
//在用户名输入框中按下 return 键
username.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {
[weak self] (_) in
self?.password.becomeFirstResponder()
}).disposed(by: disposeBag)
//在密码输入框中按下 return 键
password.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {
[weak self] (_) in
self?.password.resignFirstResponder()
}).disposed(by: disposeBag)
}
}
实例95:Rxswift实现textField事件监听
- Rxswift代码
UITextView
实例100:
- 传统代码
- Rxswift代码
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
@IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
//开始编辑响应
textView.rx.didBeginEditing
.subscribe(onNext: {
print("开始编辑")
})
.disposed(by: disposeBag)
//结束编辑响应
textView.rx.didEndEditing
.subscribe(onNext: {
print("结束编辑")
})
.disposed(by: disposeBag)
//内容发生变化响应
textView.rx.didChange
.subscribe(onNext: {
print("内容发生改变")
})
.disposed(by: disposeBag)
//选中部分变化响应
textView.rx.didChangeSelection
.subscribe(onNext: {
print("选中部分发生变化")
})
.disposed(by: disposeBag)
}
}
UITableView
- 传统Swift使用UITableView
实例110:
//歌曲结构体
struct Music {
let name: String //歌名
let singer: String //演唱者
init(name: String, singer: String) {
self.name = name
self.singer = singer
}
}
//歌曲列表数据源
struct MusicListViewModel {
let data = [
Music(name: "无条件", singer: "陈奕迅"),
Music(name: "你曾是少年", singer: "S.H.E"),
Music(name: "从前的我", singer: "陈洁仪"),
Music(name: "在木星", singer: "朴树"),
]
}
class ViewController: UIViewController {
//tableView对象
@IBOutlet weak var tableView: UITableView!
//歌曲列表数据源
let musicListViewModel = MusicListViewModel()
override func viewDidLoad() {
super.viewDidLoad()
//设置代理
tableView.dataSource = self
tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource {
//返回单元格数量
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return musicListViewModel.data.count
}
//返回对应的单元格
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "musicCell")!
let music = musicListViewModel.data[indexPath.row]
cell.textLabel?.text = music.name
cell.detailTextLabel?.text = music.singer
return cell
}
}
extension ViewController: UITableViewDelegate {
//单元格点击
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("你选中的歌曲信息【\(musicListViewModel.data[indexPath.row])】")
}
}
- Rxswift 的UITableView
实例111:
/*这里我们将 data 属性变成一个可观察序列对象(Observable Squence),
而对象当中的内容和我们之前在数组当中所包含的内容是完全一样的。
*/
//歌曲列表数据源
struct MusicListViewModel {
let data = Observable.just([
Music(name: "无条件", singer: "陈奕迅"),
Music(name: "你曾是少年", singer: "S.H.E"),
Music(name: "从前的我", singer: "陈洁仪"),
Music(name: "在木星", singer: "朴树"),
])
}
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
//tableView对象
@IBOutlet weak var tableView: UITableView!
//歌曲列表数据源
let musicListViewModel = MusicListViewModel()
//负责对象销毁
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
//将数据源数据绑定到tableView上
musicListViewModel.data
.bind(to:
/*
rx.items(cellIdentifier:):这是 Rx 基于 cellForRowAt 数据源方法的一个封装。
传统方式中我们还要有个 numberOfRowsInSection 方法,
使用 Rx 后就不再需要了(Rx 已经帮我们完成了相关工作)。
*/
tableView.rx.items(cellIdentifier:"musicCell")) { _, music, cell in
cell.textLabel?.text = music.name
cell.detailTextLabel?.text = music.singer
}.disposed(by: disposeBag)
//tableView点击响应
/*
rx.modelSelected: 这是 Rx 基于 UITableView委托回调方法 didSelectRowAt 的一个封装。
*/ tableView.rx.modelSelected(Music.self).subscribe(onNext: { music in
print("你选中的歌曲信息【\(music)】")
}).disposed(by: disposeBag)
/*DisposeBag:作用是 Rx 在视图控制器或者其持有者将要销毁的时候,
自动释法掉绑定在它上面的资源。
它是通过类似“订阅处置机制”方式实现(类似于 NotificationCenter 的 removeObserver)。*/
}
}
UICollectionView
实例120:
- 传统代码
- Rxswift代码
UIPickerView
实例130:
- 传统代码
- Rxswift代码
UIDatePicker
实例140:
- 传统代码
- Rxswift代码
UISlider
实例150:
- 传统代码
- Rxswift代码
UIStepper
实例160:
- 传统代码
- Rxswift代码
网友评论