RxSwift學習之十二 (基礎使用篇 3- UI控件擴展)

Rxswift(一)函數響應式編程思想php

RxSwift (二)序列核心邏輯分析react

RxSwift (三)Observable的建立,訂閱,銷燬編程

RxSwift(四)高階函數swift

RxSwift(五)(Rxswift對比swift,oc用法)markdown

Rxswift (六)銷燬者Dispose源碼分析網絡

RxSwift(七)Rxswift對比swift用法框架

RxSwift (十) 基礎使用篇 1- 序列,訂閱,銷燬async

RxSwift學習之十二 (基礎使用篇 3- UI控件擴展)ide

2. Rxswift基本使用之 UI控件擴展

RxSwift 是一個用於與 Swift 語言交互的框架,但它只是基礎,並不能用來進行用戶交互、網絡請求等。 而 RxCocoa 是讓 Cocoa APIs 更容易使用響應式編程的一個框架。RxCocoa 可以讓咱們方便地進行響應式網絡請求、響應式的用戶交互、綁定數據模型到 UI 控件等等。並且大多數的 UIKit 控件都有響應式擴展,它們都是經過 rx 屬性進行使用。函數

2.17 UILabel rx 擴展

2.17.1 將數據綁定到 text 屬性上(普通文本)

  • 實例2.17.1
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)
    }
}
複製代碼

2.17.2 將數據綁定到 attributedText 屬性上(富文本)

  • 實例2.17.2
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
    }
}
複製代碼

2.18 UITextField,UITextView Rx擴展

2.18.1 監聽單個 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() {
         
        //建立文本輸入框
        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 特性,不會產生大量的邏輯操做,而是以一個小的合理的幅度去執行。好比作一些實時搜索功能時,這個特性頗有用。

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)
    }
}
複製代碼

2.18.3 同時監聽多個 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 事件監聽

  • (1)經過 rx.controlEvent 能夠監聽輸入框的各類事件,且多個事件狀態能夠自由組合。除了各類 UI 控件都有的 touch 事件外,輸入框還有以下幾個獨有的事件:

editingDidBegin:開始編輯(開始輸入內容) editingChanged:輸入內容發生改變 editingDidEnd:結束編輯 editingDidEndOnExit:按下 return 鍵結束編輯 allEditingEvents:包含前面的全部編輯相關事件

  • (2)下面代碼監聽輸入框開始編輯事件(獲取到焦點)並作相應的響應。

實例2.18.4.1

textField.rx.controlEvent([.editingDidBegin]) //狀態能夠組合
    .asObservable()
    .subscribe(onNext: { _ in
        print("開始編輯內容!")
    }).disposed(by: disposeBag)
複製代碼
  • (3)下面代碼咱們在界面上添加兩個輸入框分別用於輸入用戶名和密碼:
  1. 若是當前焦點在用戶名輸入框時,按下 return 鍵時焦點自動轉移到密碼輸入框上。
  2. 若是當前焦點在密碼輸入框時,按下 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:編輯內容發生改變 didChangeSelection:選中部分發生變化

  • 實例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)
         
        //內容發生變化響應
        textView.rx.didChange
            .subscribe(onNext: {
                print("內容發生改變")
            })
            .disposed(by: disposeBag)
         
        //選中部分變化響應
        textView.rx.didChangeSelection
            .subscribe(onNext: {
                print("選中部分發生變化")
            })
            .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)
    }
}
複製代碼

或者這樣實現點擊事件訂閱

//按鈕點擊響應
button.rx.tap
    .bind { [weak self] in
        self?.showMessage("按鈕被點擊")
    }
    .disposed(by: disposeBag)
複製代碼

2.19.2 按鈕標題(title)的綁定

  • 實例2.19.2
//建立一個計時器(每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)
複製代碼

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() {
        //建立一個計時器(每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
    }
}
複製代碼

2.19.4 按鈕圖標(image)的綁定

  • 實例2.19.4
//建立一個計時器(每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)
複製代碼

2.19.5 按鈕背景圖片(backgroundImage)的綁定

  • 實例2.19.5
//建立一個計時器(每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)
複製代碼

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! }
         
        //建立一個可觀察序列,它能夠發送最後一次點擊的按鈕(也就是咱們須要選中的按鈕)
        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)
        }
    }
}
複製代碼

2.20 UISwitch、UISegmentedControl 的Rx擴展

2.20.1 UISwitch

  • 實例 2.20.1

實現當 switch 開關狀態改變時,輸出當前值。

switch1.rx.isOn.asObservable()
    .subscribe(onNext: {
        print("當前開關狀態:\($0)")
    })
    .disposed(by: disposeBag)
複製代碼
switch1.rx.isOn
    .bind(to: button1.rx.isEnabled)
    .disposed(by: disposeBag)
複製代碼

2.20.2 UISegmentedControl

  • 實例 2.20.2

實現當 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)
    }
}
複製代碼

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 當開關打開時,頂部狀態欄上會有個菊花狀的聯網指示器。 當開關關閉時,聯網指示器消失。
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使用,基本全部的綁定都是單向的。但有時候咱們須要實現雙向綁定。好比將控件的某個屬性值與 ViewModel 裏的某個 Subject 屬性進行雙向綁定:
  1. 這樣當 ViewModel 裏的值發生改變時,能夠同步反映到控件上。
  2. 而若是對控件值作修改,ViewModel 那邊值同時也會發生變化。

2.23.1 簡單的雙向綁定

  • 實例2.23.1
    image

(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)
    }
}
複製代碼

2.23.2 自定義雙向綁定操做符(operator)

  • RxSwift 自帶的雙向綁定操做符

(1)若是常常進行雙向綁定的話,最好仍是自定義一個 operator 方便使用。 (2)好在 RxSwift 項目文件夾中已經有個現成的(Operators.swift),咱們將它複製到咱們項目中便可使用。固然如咱們想本身寫一些其它的雙向綁定 operator 也能夠參考它。

image

  • 實例2.23.2 雙向綁定操做符是:<->。咱們修改上面實例2.23.1,能夠發現代碼精簡了許多
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 實現點擊頁面任意位置,輸入框便失去焦點
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 的整數倍,好比設置爲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)
    }
}
複製代碼

2.26

2.27

2.28

2.29

相關文章
相關標籤/搜索