RxSwift学习:RxSwift对比Swift,OC用法(五)
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
实例12:
传统代码
复制代码
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
实例13:
传统代码
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) } }复制代码
UISwitch
实例14:
传统代码
复制代码
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
实例15:
传统代码
复制代码
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) } }复制代码
UITextField
实例16: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)复制代码
实例17: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) } }复制代码
实例18: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) } }复制代码
实例19: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) } }复制代码
实例20: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) } }复制代码
UITextView
实例21:
传统代码
复制代码
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
实例22:
//歌曲结构体 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
实例23:
/*这里我们将 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)。*/ } }
作者:iOS沐橙君
链接:https://juejin.cn/post/7018461629012508685