RxSwift

RxSwift学习之十二 (基础使用篇 3- UI控件扩展)

2019-10-18  本文已影响0人  孔雨露

@[TOC](RxSwift学习之十二 (基础使用篇 3- UI控件扩展))

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

RxSwift 是一个用于与 Swift 语言交互的框架,但它只是基础,并不能用来进行用户交互、网络请求等。
RxCocoa 是让 Cocoa APIs 更容易使用响应式编程的一个框架。RxCocoa 能够让我们方便地进行响应式网络请求、响应式的用户交互、绑定数据模型到 UI 控件等等。而且大多数的 UIKit 控件都有响应式扩展,它们都是通过 rx 属性进行使用。

2.17 UILabel rx 扩展

2.17.1 将数据绑定到 text 属性上(普通文本)

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 属性上(富文本)

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 同理)

注意:.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)

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 同理)

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 事件监听

editingDidBegin:开始编辑(开始输入内容)
editingChanged:输入内容发生改变
editingDidEnd:结束编辑
editingDidEndOnExit:按下 return 键结束编辑
allEditingEvents:包含前面的所有编辑相关事件

实例2.18.4.1

textField.rx.controlEvent([.editingDidBegin]) //状态可以组合
    .asObservable()
    .subscribe(onNext: { _ in
        print("开始编辑内容!")
    }).disposed(by: disposeBag)
  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 独有的方法

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)
    }
}

2.19 UIButton 与 UIBarButtonItem 的Rx扩展

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)的绑定

//创建一个计时器(每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)的绑定

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)的绑定

//创建一个计时器(每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)的绑定

//创建一个计时器(每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)的绑定

switch1.rx.isOn
    .bind(to: button1.rx.isEnabled)
    .disposed(by: disposeBag)

2.19.7 按钮是否选中(isSelected)的绑定

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

实现当 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

实现当 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

通过开关我们可以控制活动指示器是否显示旋转

mySwitch.rx.value
    .bind(to: activityIndicator.rx.isAnimating)
    .disposed(by: disposeBag)

2.21.2 UIApplication

mySwitch.rx.value
    .bind(to: UIApplication.shared.rx.isNetworkActivityIndicatorVisible)
    .disposed(by: disposeBag)

2.22 UISlider、UIStepper 的Rx扩展

2.22.1 UISlider

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

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 双向绑定:<->

  1. 这样当 ViewModel 里的值发生改变时,可以同步反映到控件上。
  2. 而如果对控件值做修改,ViewModel 那边值同时也会发生变化。

2.23.1 简单的双向绑定

(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)

(1)如果经常进行双向绑定的话,最好还是自定义一个 operator 方便使用。
(2)好在 RxSwift 项目文件夹中已经有个现成的(Operators.swift),我们将它复制到我们项目中即可使用。当然如我们想自己写一些其它的双向绑定 operator 也可以参考它。

image
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

(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)
    }
} 

2.25 UIDatePicker

2.25.1 日期选择响应

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 倒计时功能

通过上方的 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

上一篇 下一篇

猜你喜欢

热点阅读