@[TOC](RxSwift學習之十二 (基礎使用篇 3- UI控件擴展))
2. Rxswift基本使用之 UI控件擴展
RxSwift是一個用于與Swift語言交互的框架,但它只是基礎,并不能用來進行用戶交互、網(wǎng)絡請求等。
而RxCocoa是讓Cocoa APIs更容易使用響應式編程的一個框架。RxCocoa能夠讓我們方便地進行響應式網(wǎng)絡請求、響應式的用戶交互、綁定數(shù)據(jù)模型到 UI 控件等等。而且大多數(shù)的 UIKit 控件都有響應式擴展,它們都是通過 rx 屬性進行使用。
2.17 UILabel rx 擴展
2.17.1 將數(shù)據(jù)綁定到 text 屬性上(普通文本)
- 實例2.17.1
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//創(chuàng)建文本標簽
let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))
self.view.addSubview(label)
//創(chuàng)建一個計時器(每0.1秒發(fā)送一個索引數(shù))
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)
}
}
2.17.2 將數(shù)據(jù)綁定到 attributedText 屬性上(富文本)
- 實例2.17.2
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//創(chuàng)建文本標簽
let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))
self.view.addSubview(label)
//創(chuàng)建一個計時器(每0.1秒發(fā)送一個索引數(shù))
let timer = Observable<Int>.interval(0.1, scheduler: MainScheduler.instance)
//將已過去的時間格式化成想要的字符串,并綁定到label上
timer.map(formatTimeInterval)
.bind(to: label.rx.attributedText)
.disposed(by: disposeBag)
}
//將數(shù)字轉成對應的富文本
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
}
}
2.18 UITextField,UITextView Rx擴展
2.18.1 監(jiān)聽單個 textField 內容的變化(textView 同理)
- 實例2.18.1 :將 textField 里輸入的內容實時地顯示到控制臺中
注意:.orEmpty 可以將 String? 類型的 ControlProperty 轉成 String,省得我們再去解包。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//創(chuàng)建文本輸入框
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)
}
}
- 當然我們直接使用 change 事件效果也是一樣的。
//當文本框內容改變時,將內容輸出到控制臺上
textField.rx.text.orEmpty.changed
.subscribe(onNext: {
print("您輸入的是:\($0)")
})
.disposed(by: disposeBag)
2.18.2 將內容綁定到其他控件上
- 實例2.18.2
Throttling 的作用:
Throttling 是 RxSwift 的一個特性。因為有時當一些東西改變時,通常會做大量的邏輯操作。而使用 Throttling 特性,不會產生大量的邏輯操作,而是以一個小的合理的幅度去執(zhí)行。比如做一些實時搜索功能時,這個特性很有用。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
override func viewDidLoad() {
//創(chuàng)建文本輸入框
let inputField = UITextField(frame: CGRect(x:10, y:80, width:200, height:30))
inputField.borderStyle = UITextBorderStyle.roundedRect
self.view.addSubview(inputField)
//創(chuàng)建文本輸出框
let outputField = UITextField(frame: CGRect(x:10, y:150, width:200, height:30))
outputField.borderStyle = UITextBorderStyle.roundedRect
self.view.addSubview(outputField)
//創(chuàng)建文本標簽
let label = UILabel(frame:CGRect(x:20, y:190, width:300, height:30))
self.view.addSubview(label)
//創(chuàng)建按鈕
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{ "當前字數(shù):\($0.count)" }
.drive(label.rx.text)
.disposed(by: disposeBag)
//根據(jù)內容字數(shù)決定按鈕是否可用
input.map{ $0.count > 5 }
.drive(button.rx.isEnabled)
.disposed(by: disposeBag)
}
}
2.18.3 同時監(jiān)聽多個 textField 內容的變化(textView 同理)
- 實例2.18.3
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)
}
}
2.18.4 事件監(jiān)聽
- (1)通過 rx.controlEvent 可以監(jiān)聽輸入框的各種事件,且多個事件狀態(tài)可以自由組合。除了各種 UI 控件都有的 touch 事件外,輸入框還有如下幾個獨有的事件:
editingDidBegin:開始編輯(開始輸入內容)
editingChanged:輸入內容發(fā)生改變
editingDidEnd:結束編輯
editingDidEndOnExit:按下 return 鍵結束編輯
allEditingEvents:包含前面的所有編輯相關事件
- (2)下面代碼監(jiān)聽輸入框開始編輯事件(獲取到焦點)并做相應的響應。
實例2.18.4.1
textField.rx.controlEvent([.editingDidBegin]) //狀態(tài)可以組合
.asObservable()
.subscribe(onNext: { _ in
print("開始編輯內容!")
}).disposed(by: disposeBag)
- (3)下面代碼我們在界面上添加兩個輸入框分別用于輸入用戶名和密碼:
- 如果當前焦點在用戶名輸入框時,按下 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)
}
}
2.18.5 UITextView 獨有的方法
- UITextView 還封裝了如下幾個委托回調方法:
didBeginEditing:開始編輯
didEndEditing:結束編輯
didChange:編輯內容發(fā)生改變
didChangeSelection:選中部分發(fā)生變化
- 實例2.18.5
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)
//內容發(fā)生變化響應
textView.rx.didChange
.subscribe(onNext: {
print("內容發(fā)生改變")
})
.disposed(by: disposeBag)
//選中部分變化響應
textView.rx.didChangeSelection
.subscribe(onNext: {
print("選中部分發(fā)生變化")
})
.disposed(by: disposeBag)
}
}
2.19 UIButton 與 UIBarButtonItem 的Rx擴展
2.19.1 按鈕點擊響應
- 實例2.19.1
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)
}
}
或者這樣實現(xiàn)點擊事件訂閱
//按鈕點擊響應
button.rx.tap
.bind { [weak self] in
self?.showMessage("按鈕被點擊")
}
.disposed(by: disposeBag)
2.19.2 按鈕標題(title)的綁定
- 實例2.19.2
//創(chuàng)建一個計時器(每1秒發(fā)送一個索引數(shù))
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
//根據(jù)索引數(shù)拼接最新的標題,并綁定到button上
timer.map{"計數(shù)\($0)"}
.bind(to: button.rx.title(for: .normal))// rx.title 為 setTitle(_:for:) 的封裝。
.disposed(by: disposeBag)
2.19.3 按鈕富文本標題(attributedTitle)的綁定
- 實例2.19.3
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag()
@IBOutlet weak var button: UIButton!
override func viewDidLoad() {
//創(chuàng)建一個計時器(每1秒發(fā)送一個索引數(shù))
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)
}
//將數(shù)字轉成對應的富文本
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
}
}
2.19.4 按鈕圖標(image)的綁定
- 實例2.19.4
//創(chuàng)建一個計時器(每1秒發(fā)送一個索引數(shù))
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
//根據(jù)索引數(shù)選擇對應的按鈕圖標,并綁定到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)
2.19.5 按鈕背景圖片(backgroundImage)的綁定
- 實例2.19.5
//創(chuàng)建一個計時器(每1秒發(fā)送一個索引數(shù))
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
//根據(jù)索引數(shù)選擇對應的按鈕背景圖,并綁定到button上
timer.map{ UIImage(named: "\($0%2)")! }
.bind(to: button.rx.backgroundImage()) //rx.backgroundImage 為 setBackgroundImage(_:for:) 的封裝。
.disposed(by: disposeBag)
2.19.6 按鈕是否可用(isEnabled)的綁定
- 實例2.19.6
switch1.rx.isOn
.bind(to: button1.rx.isEnabled)
.disposed(by: disposeBag)
2.19.7 按鈕是否選中(isSelected)的綁定
- 實例2.19.7
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! }
//創(chuàng)建一個可觀察序列,它可以發(fā)送最后一次點擊的按鈕(也就是我們需要選中的按鈕)
let selectedButton = Observable.from(
buttons.map { button in button.rx.tap.map { button } }
).merge()
//對于每一個按鈕都對selectedButton進行訂閱,根據(jù)它是否是當前選中的按鈕綁定isSelected屬性
for button in buttons {
selectedButton.map { $0 == button }
.bind(to: button.rx.isSelected)
.disposed(by: disposeBag)
}
}
}
2.20 UISwitch、UISegmentedControl 的Rx擴展
2.20.1 UISwitch
- 實例 2.20.1
實現(xiàn)當 switch 開關狀態(tài)改變時,輸出當前值。
switch1.rx.isOn.asObservable()
.subscribe(onNext: {
print("當前開關狀態(tài):\($0)")
})
.disposed(by: disposeBag)
switch1.rx.isOn
.bind(to: button1.rx.isEnabled)
.disposed(by: disposeBag)
2.20.2 UISegmentedControl
- 實例 2.20.2
實現(xiàn)當 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() {
//創(chuàng)建一個當前需要顯示的圖片的可觀察序列
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)
}
}
2.21 UIActivityIndicatorView、UIApplication 的Rx擴展
2.21.1 UIActivityIndicatorView
- 實例2.21.1
通過開關我們可以控制活動指示器是否顯示旋轉
mySwitch.rx.value
.bind(to: activityIndicator.rx.isAnimating)
.disposed(by: disposeBag)
2.21.2 UIApplication
- 實例2.21.2
當開關打開時,頂部狀態(tài)欄上會有個菊花狀的聯(lián)網(wǎng)指示器。
當開關關閉時,聯(lián)網(wǎng)指示器消失。
mySwitch.rx.value
.bind(to: UIApplication.shared.rx.isNetworkActivityIndicatorVisible)
.disposed(by: disposeBag)
2.22 UISlider、UIStepper 的Rx擴展
2.22.1 UISlider
- 實例2.22.1
拖動滑塊時,在控制臺中實時輸出 slider 當前值
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)
}
}
2.22.2 UIStepper
- 實例2.22.2
當 stepper 值改變時,在控制臺中實時輸出當前值。
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)
2.23 雙向綁定:<->
- 前面講到的UI使用,基本所有的綁定都是單向的。但有時候我們需要實現(xiàn)雙向綁定。比如將控件的某個屬性值與
ViewModel里的某個Subject屬性進行雙向綁定:
- 這樣當 ViewModel 里的值發(fā)生改變時,可以同步反映到控件上。
- 而如果對控件值做修改,ViewModel 那邊值同時也會發(fā)生變化。
2.23.1 簡單的雙向綁定
-
實例2.23.1
image
(1)頁面上方是一個文本輸入框,用于填寫用戶名。它與 VM 里的 username 屬性做雙向綁定。
(2)下方的文本標簽會根據(jù)用戶名顯示對應的用戶信息。(只有 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)
}
}
2.23.2 自定義雙向綁定操作符(operator)
- RxSwift 自帶的雙向綁定操作符
(1)如果經(jīng)常進行雙向綁定的話,最好還是自定義一個
operator方便使用。
(2)好在RxSwift項目文件夾中已經(jīng)有個現(xiàn)成的(Operators.swift),我們將它復制到我們項目中即可使用。當然如我們想自己寫一些其它的雙向綁定 operator 也可以參考它。
- 實例2.23.2
雙向綁定操作符是:<->。我們修改上面實例2.23.1,可以發(fā)現(xiàn)代碼精簡了許多
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)
}
}
2.24 UIGestureRecognizer
- 實例2.24.1
當手指在界面上向上滑動時,彈出提示框,并顯示出滑動起點的坐標。
(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)
}
}
- 實例2.24.2
實現(xiàn)點擊頁面任意位置,輸入框便失去焦點
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)
}
}
2.25 UIDatePicker
2.25.1 日期選擇響應
-
實例2.25.1
當日期選擇器里面的時間改變后,將時間格式化顯示到 label 中
image
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)
}
}
2.25.2 倒計時功能
-
實例2.25.2
image
通過上方的 datepicker 選擇需要倒計時的時間后,點擊“開始”按鈕即可開始倒計時。
倒計時過程中,datepicker 和按鈕都不可用。且按鈕標題變成顯示倒計時剩余時間。
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
//倒計時時間選擇控件
var ctimer:UIDatePicker!
//開始按鈕
var btnstart:UIButton!
//剩余時間(必須為 60 的整數(shù)倍,比如設置為100,值自動變?yōu)?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 是為了解決第一次撥動表盤不觸發(fā)值改變事件的問題
_ = self.ctimer.rx.countDownDuration <-> self.leftTime //<-> 是自定義的雙向綁定符號
}
//綁定button標題
Observable.combineLatest(leftTime.asObservable(), countDownStopped.asObservable()) {
leftTimeValue, countDownStoppedValue in
//根據(jù)當前的狀態(tài)設置按鈕的標題
if countDownStoppedValue {
return "開始"
}else{
return "倒計時開始,還有 \(Int(leftTimeValue)) 秒..."
}
}.bind(to: btnstart.rx.title())
.disposed(by: disposeBag)
//綁定button和datepicker狀態(tài)(在倒計過程中,按鈕和時間選擇組件不可用)
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
//創(chuàng)建一個計時器
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)
}
}