RxSwift (二)序列核心逻辑分析react
RxSwift (三)Observable的建立,订阅,销毁编程
RxSwift(四)高阶函数swift
RxSwift(五)(Rxswift对比swift,oc用法)markdown
RxSwift (十) 基础使用篇 1- 序列,订阅,销毁async
RxSwift学习之十二 (基础使用篇 3- UI控件扩展)ide
RxSwift
是一个用于与Swift
语言交互的框架,但它只是基础,并不能用来进行用户交互、网络请求等。 而RxCocoa
是让Cocoa APIs
更容易使用响应式编程的一个框架。RxCocoa
可以让咱们方便地进行响应式网络请求、响应式的用户交互、绑定数据模型到 UI 控件等等。并且大多数的 UIKit 控件都有响应式扩展,它们都是经过 rx 属性进行使用。函数
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) } } 复制代码
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 } } 复制代码
注意:.orEmpty 能够将 String? 类型的 ControlProperty 转成 String,免得咱们再去解包。
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) 复制代码
Throttling 的做用: Throttling 是 RxSwift 的一个特性。由于有时当一些东西改变时,一般会作大量的逻辑操做。而使用 Throttling 特性,不会产生大量的逻辑操做,而是以一个小的合理的幅度去执行。好比作一些实时搜索功能时,这个特性颇有用。
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) } } 复制代码
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) } } 复制代码
editingDidBegin
:开始编辑(开始输入内容)editingChanged
:输入内容发生改变editingDidEnd
:结束编辑editingDidEndOnExit
:按下 return 键结束编辑allEditingEvents
:包含前面的全部编辑相关事件
实例2.18.4.1
textField.rx.controlEvent([.editingDidBegin]) //状态能够组合 .asObservable() .subscribe(onNext: { _ in print("开始编辑内容!") }).disposed(by: disposeBag) 复制代码
- 若是当前焦点在用户名输入框时,按下 return 键时焦点自动转移到密码输入框上。
- 若是当前焦点在密码输入框时,按下 return 键时自动移除焦点。
实例2.18.4.2
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) } } 复制代码
didBeginEditing
:开始编辑didEndEditing
:结束编辑didChange
:编辑内容发生改变didChangeSelection
:选中部分发生变化
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) } } 复制代码
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var button: UIButton! override func viewDidLoad() { //按钮点击响应 button.rx.tap .subscribe(onNext: { [weak self] in self?.showMessage("按钮被点击") }) .disposed(by: disposeBag) } //显示消息提示框 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) } } 复制代码
或者这样实现点击事件订阅
//按钮点击响应 button.rx.tap .bind { [weak self] in self?.showMessage("按钮被点击") } .disposed(by: disposeBag) 复制代码
//建立一个计时器(每1秒发送一个索引数) let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance) //根据索引数拼接最新的标题,并绑定到button上 timer.map{"计数\($0)"} .bind(to: button.rx.title(for: .normal))// rx.title 为 setTitle(_:for:) 的封装。 .disposed(by: disposeBag) 复制代码
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var button: UIButton! override func viewDidLoad() { //建立一个计时器(每1秒发送一个索引数) let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance) //将已过去的时间格式化成想要的字符串,并绑定到button上 timer.map(formatTimeInterval) .bind(to: button.rx.attributedTitle()) //rx.attributedTitle 为 setAttributedTitle(_:controlState:) 的封装。 .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 } } 复制代码
//建立一个计时器(每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())// rx.image 为 setImage(_:for:) 的封装。 .disposed(by: disposeBag) 复制代码
//建立一个计时器(每1秒发送一个索引数) let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance) //根据索引数选择对应的按钮背景图,并绑定到button上 timer.map{ UIImage(named: "\($0%2)")! } .bind(to: button.rx.backgroundImage()) //rx.backgroundImage 为 setBackgroundImage(_:for:) 的封装。 .disposed(by: disposeBag) 复制代码
switch1.rx.isOn
.bind(to: button1.rx.isEnabled)
.disposed(by: disposeBag)
复制代码
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var button1: UIButton! @IBOutlet weak var button2: UIButton! @IBOutlet weak var button3: UIButton! override func viewDidLoad() { //默认选中第一个按钮 button1.isSelected = true //强制解包,避免后面还须要处理可选类型 let buttons = [button1, button2, button3].map { $0! } //建立一个可观察序列,它能够发送最后一次点击的按钮(也就是咱们须要选中的按钮) let selectedButton = Observable.from( buttons.map { button in button.rx.tap.map { button } } ).merge() //对于每个按钮都对selectedButton进行订阅,根据它是不是当前选中的按钮绑定isSelected属性 for button in buttons { selectedButton.map { $0 == button } .bind(to: button.rx.isSelected) .disposed(by: disposeBag) } } } 复制代码
实现当 switch 开关状态改变时,输出当前值。
switch1.rx.isOn.asObservable() .subscribe(onNext: { print("当前开关状态:\($0)") }) .disposed(by: disposeBag) 复制代码
switch1.rx.isOn
.bind(to: button1.rx.isEnabled)
.disposed(by: disposeBag)
复制代码
实现当 UISegmentedControl 选中项改变时,输出当前选中项索引值。
segmented.rx.selectedSegmentIndex.asObservable() .subscribe(onNext: { print("当前项:\($0)") }) .disposed(by: disposeBag) 复制代码
当 segmentedControl 选项改变时,imageView 会自动显示相应的图片。
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) } } 复制代码
经过开关咱们能够控制活动指示器是否显示旋转
mySwitch.rx.value
.bind(to: activityIndicator.rx.isAnimating)
.disposed(by: disposeBag)
复制代码
mySwitch.rx.value .bind(to: UIApplication.shared.rx.isNetworkActivityIndicatorVisible) .disposed(by: disposeBag) 复制代码
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { @IBOutlet weak var slider: UISlider! let disposeBag = DisposeBag() override func viewDidLoad() { slider.rx.value.asObservable() .subscribe(onNext: { print("当前值为:\($0)") }) .disposed(by: disposeBag) } } 复制代码
stepper.rx.value.asObservable() .subscribe(onNext: { print("当前值为:\($0)") }) .disposed(by: disposeBag) 复制代码
使用滑块(slider)来控制 stepper 的步长。
slider.rx.value .map{ Double($0) } //因为slider值为Float类型,而stepper的stepValue为Double类型,所以须要转换 .bind(to: stepper.rx.stepValue) .disposed(by: disposeBag) 复制代码
ViewModel
里的某个 Subject
属性进行双向绑定:
- 这样当 ViewModel 里的值发生改变时,能够同步反映到控件上。
- 而若是对控件值作修改,ViewModel 那边值同时也会发生变化。
(1)页面上方是一个文本输入框,用于填写用户名。它与 VM 里的 username 属性作双向绑定。 (2)下方的文本标签会根据用户名显示对应的用户信息。(只有 hangge 显示管理员,其它都是访客)
(1)首先定义一个 VM,代码以下:
import RxSwift struct UserViewModel { //用户名 let username = Variable("guest") //用户信息 lazy var userinfo = { return self.username.asObservable() .map{ $0 == "hangge" ? "您是管理员" : "您是普通访客" } .share(replay: 1) }() } 复制代码
(2)页面代码以下(高亮部分为 textfield 与 VM 的双向绑定):
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { @IBOutlet weak var textField: UITextField! @IBOutlet weak var label: UILabel! var userVM = UserViewModel() let disposeBag = DisposeBag() override func viewDidLoad() { //将用户名与textField作双向绑定 userVM.username.asObservable().bind(to: textField.rx.text).disposed(by: disposeBag) textField.rx.text.orEmpty.bind(to: userVM.username).disposed(by: disposeBag) //将用户信息绑定到label上 userVM.userinfo.bind(to: label.rx.text).disposed(by: disposeBag) } } 复制代码
(1)若是常常进行双向绑定的话,最好仍是自定义一个
operator
方便使用。 (2)好在RxSwift
项目文件夹中已经有个现成的(Operators.swift
),咱们将它复制到咱们项目中便可使用。固然如咱们想本身写一些其它的双向绑定 operator 也能够参考它。
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { @IBOutlet weak var textField: UITextField! @IBOutlet weak var label: UILabel! var userVM = UserViewModel() let disposeBag = DisposeBag() override func viewDidLoad() { //将用户名与textField作双向绑定 _ = self.textField.rx.textInput <-> self.userVM.username //将用户信息绑定到label上 userVM.userinfo.bind(to: label.rx.text).disposed(by: disposeBag) } } 复制代码
(1)第一种响应回调的写法
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //添加一个上滑手势 let swipe = UISwipeGestureRecognizer() swipe.direction = .up self.view.addGestureRecognizer(swipe) //手势响应 swipe.rx.event .subscribe(onNext: { [weak self] recognizer in //这个点是滑动的起点 let point = recognizer.location(in: recognizer.view) self?.showAlert(title: "向上划动", message: "\(point.x) \(point.y)") }) .disposed(by: disposeBag) } //显示消息提示框 func showAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "肯定", style: .cancel)) self.present(alert, animated: true) } } 复制代码
(2)第二种响应回调的写法
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //添加一个上滑手势 let swipe = UISwipeGestureRecognizer() swipe.direction = .up self.view.addGestureRecognizer(swipe) //手势响应 swipe.rx.event .bind { [weak self] recognizer in //这个点是滑动的起点 let point = recognizer.location(in: recognizer.view) self?.showAlert(title: "向上划动", message: "\(point.x) \(point.y)") } .disposed(by: disposeBag) } //显示消息提示框 func showAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "肯定", style: .cancel)) self.present(alert, animated: true) } } 复制代码
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //添加一个点击手势 let tapBackground = UITapGestureRecognizer() view.addGestureRecognizer(tapBackground) //页面上任意处点击,输入框便失去焦点 tapBackground.rx.event .subscribe(onNext: { [weak self] _ in self?.view.endEditing(true) }) .disposed(by: disposeBag) } } 复制代码
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { @IBOutlet weak var datePicker: UIDatePicker! @IBOutlet weak var label: UILabel! //日期格式化器 lazy var dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy年MM月dd日 HH:mm" return formatter }() let disposeBag = DisposeBag() override func viewDidLoad() { datePicker.rx.date .map { [weak self] in "当前选择时间: " + self!.dateFormatter.string(from: $0) } .bind(to: label.rx.text) .disposed(by: disposeBag) } } 复制代码
经过上方的 datepicker 选择须要倒计时的时间后,点击“开始”按钮便可开始倒计时。 倒计时过程当中,datepicker 和按钮都不可用。且按钮标题变成显示倒计时剩余时间。
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { //倒计时时间选择控件 var ctimer:UIDatePicker! //开始按钮 var btnstart:UIButton! //剩余时间(必须为 60 的整数倍,好比设置为100,值自动变为 60) let leftTime = Variable(TimeInterval(180)) //当前倒计时是否结束 let countDownStopped = Variable(true) let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() //初始化datepicker ctimer = UIDatePicker(frame:CGRect(x:0, y:80, width:320, height:200)) ctimer.datePickerMode = UIDatePickerMode.countDownTimer self.view.addSubview(ctimer) //初始化button btnstart = UIButton(type: .system) btnstart.frame = CGRect(x:0, y:300, width:320, height:30); btnstart.setTitleColor(UIColor.red, for: .normal) btnstart.setTitleColor(UIColor.darkGray, for:.disabled) self.view.addSubview(btnstart) //剩余时间与datepicker作双向绑定 DispatchQueue.main.async{//加 DispatchQueue.main.async 是为了解决第一次拨动表盘不触发值改变事件的问题 _ = self.ctimer.rx.countDownDuration <-> self.leftTime //<-> 是自定义的双向绑定符号 } //绑定button标题 Observable.combineLatest(leftTime.asObservable(), countDownStopped.asObservable()) { leftTimeValue, countDownStoppedValue in //根据当前的状态设置按钮的标题 if countDownStoppedValue { return "开始" }else{ return "倒计时开始,还有 \(Int(leftTimeValue)) 秒..." } }.bind(to: btnstart.rx.title()) .disposed(by: disposeBag) //绑定button和datepicker状态(在倒计过程当中,按钮和时间选择组件不可用) countDownStopped.asDriver().drive(ctimer.rx.isEnabled).disposed(by: disposeBag) countDownStopped.asDriver().drive(btnstart.rx.isEnabled).disposed(by: disposeBag) //按钮点击响应 btnstart.rx.tap .bind { [weak self] in self?.startClicked() } .disposed(by: disposeBag) } //开始倒计时 func startClicked() { //开始倒计时 self.countDownStopped.value = false //建立一个计时器 Observable<Int>.interval(1, scheduler: MainScheduler.instance) .takeUntil(countDownStopped.asObservable().filter{ $0 }) //倒计时结束时中止计时器 .subscribe { event in //每次剩余时间减1 self.leftTime.value -= 1 // 若是剩余时间小于等于0 if(self.leftTime.value == 0) { print("倒计时结束!") //结束倒计时 self.countDownStopped.value = true //重制时间 self.leftTime.value = 180 } }.disposed(by: disposeBag) } } 复制代码