Rxswift (1) Functional responsive programming idea
RxSwift (II) Sequence core logic analysis
Create, subscribe, and destroy an RxSwift Observable
RxSwift (4) higher-order functions
RxSwift (v)
(6) Dispose source code analysis
RxSwift (7) RxSwift vs. SWIFT usage
RxSwift (x) Basic Usage part 1- Sequence, subscribe, destroy
RxSwift Learning 12 (Basics 3- UI Control Extensions)
2. UI control extensions for basic use by Rxswift
RxSwift is a framework for interacting with the Swift language, but it is only basic and cannot be used for user interaction, network requests, etc. RxCocoa is a framework for making responsive programming easier to use with Cocoa APIs. RxCocoa makes it easy to make responsive network requests, responsive user interactions, bind data models to UI controls, and more. And most UIKit controls have reactive extensions, which are used through rX properties.
2.17 UILabel RX extension
2.17.1 Binding Data to a Text Property (Plain Text)
- Instance 2.17.1
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
// Create a text label
let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))
self.view.addSubview(label)
// Create a timer (send an index every 0.1 seconds)
let timer = Observable<Int>.interval(0.1, scheduler: MainScheduler.instance)
// Formats the elapsed time into the desired string and binds it to the label
timer.map{ String(format: "% % 0.2 d: % d. 0.2 0.1 d",
arguments: [($0 / 600) % 600, ($0 % 600 ) / 10, $0 % 10]) }
.bind(to: label.rx.text)
.disposed(by: disposeBag)
}
}
Copy the code
2.17.2 Binding Data to an attributedText Attribute (Rich Text)
- Instance 2.17.2
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
// Create a text label
let label = UILabel(frame:CGRect(x:20, y:40, width:300, height:100))
self.view.addSubview(label)
// Create a timer (send an index every 0.1 seconds)
let timer = Observable<Int>.interval(0.1, scheduler: MainScheduler.instance)
// Formats the elapsed time into the desired string and binds it to the label
timer.map(formatTimeInterval)
.bind(to: label.rx.attributedText)
.disposed(by: disposeBag)
}
// Convert numbers into corresponding rich text
func formatTimeInterval(ms: NSInteger) -> NSMutableAttributedString {
let string = String(format: "% % 0.2 d: % d. 0.2 0.1 d",
arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])
// Rich text Settings
let attributeString = NSMutableAttributedString(string: string)
// Start from text 0 with 6 character font helveticaneue-bold, size 16
attributeString.addAttribute(NSAttributedStringKey.font,
value: UIFont(name: "HelveticaNeue-Bold", size: 16)! , range:NSMakeRange(0.5))
// Set the font color
attributeString.addAttribute(NSAttributedStringKey.foregroundColor,
value: UIColor.white, range: NSMakeRange(0.5))
// Set the text background color
attributeString.addAttribute(NSAttributedStringKey.backgroundColor,
value: UIColor.orange, range: NSMakeRange(0.5))
return attributeString
}
}
Copy the code
2.18 UITextField, UITextView Rx extension
2.18.1 Listening for changes in the contents of a Single textField (same with textView)
- Example 2.18.1: Display input from textField to the console in real time
Note:.orempty can combine String? Type ControlProperty to String, save us unpacking.
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
// Create a text input box
let textField = UITextField(frame: CGRect(x:10, y:80, width:200, height:30))
textField.borderStyle = UITextBorderStyle.roundedRect
self.view.addSubview(textField)
// When the content of the text box changes, output the content to the console
textField.rx.text.orEmpty.asObservable()
.subscribe(onNext: {
print("What you typed is:\ [$0)")
})
.disposed(by: disposeBag)
}
}
Copy the code
- Of course, if we use the change event directly, the effect is the same.
// When the content of the text box changes, output the content to the console
textField.rx.text.orEmpty.changed
.subscribe(onNext: {
print("What you typed is:\ [$0)")
})
.disposed(by: disposeBag)
Copy the code
2.18.2 Binding Content to Other controls
- Instance 2.18.2
What Throttling does: Throttling is a feature of RxSwift. Because sometimes when something changes, it usually does a lot of logic. Using Throttling features, however, does not result in a large number of logical operations, but rather in a small, reasonable range of operations. This feature is useful for real-time search functions, for example.
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
// Create a text input box
let inputField = UITextField(frame: CGRect(x:10, y:80, width:200, height:30))
inputField.borderStyle = UITextBorderStyle.roundedRect
self.view.addSubview(inputField)
// Create a text output box
let outputField = UITextField(frame: CGRect(x:10, y:150, width:200, height:30))
outputField.borderStyle = UITextBorderStyle.roundedRect
self.view.addSubview(outputField)
// Create a text label
let label = UILabel(frame:CGRect(x:20, y:190, width:300, height:30))
self.view.addSubview(label)
// Create button
let button:UIButton = UIButton(type:.system)
button.frame = CGRect(x:20, y:230, width:40, height:30)
button.setTitle("Submit".for:.normal)
self.view.addSubview(button)
// When the text box content changes
let input = inputField.rx.text.orEmpty.asDriver() // Convert a normal sequence to a Driver
.throttle(0.3) // If the value changes several times within 0.3 seconds, take the last time
// The content is bound to another input box
input.drive(outputField.rx.text)
.disposed(by: disposeBag)
// The content is bound to the text tag
input.map{ "Current word count:\ [$0.count)" }
.drive(label.rx.text)
.disposed(by: disposeBag)
// Buttons are available based on the number of words
input.map{$0.count > 5 }
.drive(button.rx.isEnabled)
.disposed(by: disposeBag)
}
}
Copy the code
2.18.3 Monitoring changes of Multiple TextFields at the same time (same with textView)
- Instance 2.18.3
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)@IBOutlet weak var textField1: UITextField!
@IBOutlet weak var textField2: UITextField!
@IBOutlet weak var label: UILabel!
override func viewDidLoad(a) {
Observable.combineLatest(textField1.rx.text.orEmpty, textField2.rx.text.orEmpty) {
textValue1, textValue2 -> String in
return "The number you typed in was:\(textValue1)-\(textValue2)"}.map{$0 }
.bind(to: label.rx.text)
.disposed(by: disposeBag)
}
}
Copy the code
2.18.4 Event Listening
- ControlEvent Rx. ControlEvent Can listen to various events in the input box, and multiple event states can be freely combined. In addition to the touch events common to all UI controls, the input box has the following unique events:
EditingDidBegin: editingDidBegin: editingDidBegin: editingChanged: editingDidEnd: editingDidEndOnExit: Press the Return key to end editing allEditingEvents: contains all previous edit-related events
- (2) The following code listens to the input box to start editing the event (to get focus) and responds accordingly.
Instance 2.18.4.1
textField.rx.controlEvent([.editingDidBegin]) // States can be combined
.asObservable()
.subscribe(onNext: { _ in
print("Start editing!")
}).disposed(by: disposeBag)
Copy the code
- (3) In the following code, we add two input boxes on the interface respectively for entering the user name and password:
- If the current focus is on the user name input box, press the Return key to switch the focus to the password input box.
- If the current focus is in the password input box, the focus is automatically removed when you press return.
Instance 2.18.4.2
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
// User name input box
@IBOutlet weak var username: UITextField!
// Password input box
@IBOutlet weak var password: UITextField!
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
super.viewDidLoad()
// Press the Return key in the username input field
username.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {
[weak self] (_) in
self? .password.becomeFirstResponder() }).disposed(by: disposeBag)// Press the return key in the password input box
password.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {
[weak self] (_) in
self? .password.resignFirstResponder() }).disposed(by: disposeBag) } }Copy the code
2.18.5 UITextView unique method
- UITextView also encapsulates the following delegate callback methods:
DidChange: didChangeSelection: the selected part has changed
- Instance 2.18.5
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)@IBOutlet weak var textView: UITextView!
override func viewDidLoad(a) {
// Start editing the response
textView.rx.didBeginEditing
.subscribe(onNext: {
print("Start editing")
})
.disposed(by: disposeBag)
// Finish editing the response
textView.rx.didEndEditing
.subscribe(onNext: {
print("End of edit")
})
.disposed(by: disposeBag)
// The content changes in response
textView.rx.didChange
.subscribe(onNext: {
print("The content has changed")
})
.disposed(by: disposeBag)
// Select part of the change response
textView.rx.didChangeSelection
.subscribe(onNext: {
print("The selected part changes.")
})
.disposed(by: disposeBag)
}
}
Copy the code
Rx extension of UIButton and UIBarButtonItem
2.19.1 Button Click Response
- Instance 2.19.1
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)@IBOutlet weak var button: UIButton!
override func viewDidLoad(a) {
// Button click response
button.rx.tap
.subscribe(onNext: { [weak self] in
self? .showMessage("Button clicked")
})
.disposed(by: disposeBag)
}
// Displays a message prompt box
func showMessage(_ text: String) {
let alertController = UIAlertController(title: text, message: nil, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Sure", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)}}Copy the code
Or you can subscribe to click events this way
// Button click response
button.rx.tap
.bind { [weak self] in
self? .showMessage("Button clicked")
}
.disposed(by: disposeBag)
Copy the code
2.19.2 Binding of button Title
- Instance 2.19.2
// Create a timer (send an index every 1 second)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
// Concatenate the latest title according to the index number and bind it to the button
timer.map{"Count\ [$0)"}
.bind(to: button.rx.title(for: .normal))// rx.title is the wrapper of setTitle(_:for:).
.disposed(by: disposeBag)
Copy the code
2.19.3 Binding of Button’s attributedTitle
- Instance 2.19.3
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)@IBOutlet weak var button: UIButton!
override func viewDidLoad(a) {
// Create a timer (send an index every 1 second)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
// Format the past time into the desired string and bind it to the button
timer.map(formatTimeInterval)
.bind(to: button.rx.attributedTitle()) //rx.attributedTitle is an encapsulation of setAttributedTitle(_:controlState:).
.disposed(by: disposeBag)
}
// Convert numbers into corresponding rich text
func formatTimeInterval(ms: NSInteger) -> NSMutableAttributedString {
let string = String(format: "% % 0.2 d: % d. 0.2 0.1 d",
arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])
// Rich text Settings
let attributeString = NSMutableAttributedString(string: string)
// Start from text 0 with 6 character font helveticaneue-bold, size 16
attributeString.addAttribute(NSAttributedStringKey.font,
value: UIFont(name: "HelveticaNeue-Bold", size: 16)! , range:NSMakeRange(0.5))
// Set the font color
attributeString.addAttribute(NSAttributedStringKey.foregroundColor,
value: UIColor.white, range: NSMakeRange(0.5))
// Set the text background color
attributeString.addAttribute(NSAttributedStringKey.backgroundColor,
value: UIColor.orange, range: NSMakeRange(0.5))
return attributeString
}
}
Copy the code
2.19.4 Binding of a Button Icon (Image
- Instance 2.19.4
// Create a timer (send an index every 1 second)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
// Select the corresponding button icon according to the index number and bind it to the button
timer.map({
let name = $0%2= =0 ? "back" : "forward"
return UIImage(named: name)!
})
.bind(to: button.rx.image())// rx.image is the encapsulation of setImage(_:for:).
.disposed(by: disposeBag)
Copy the code
2.19.5 binding of button backgroundImage
- Instance 2.19.5
// Create a timer (send an index every 1 second)
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
// Select the corresponding button background image based on the index number and bind it to the button
timer.map{ UIImage(named: "\ [$0%2)")! }
.bind(to: button.rx.backgroundImage()) //rx.backgroundImage is the encapsulation of setBackgroundImage(_:for:).
.disposed(by: disposeBag)
Copy the code
2.19.6 Whether button Binding is Available (isEnabled)
- Instance 2.19.6
switch1.rx.isOn
.bind(to: button1.rx.isEnabled)
.disposed(by: disposeBag)
Copy the code
2.19.7 Button Whether to select the binding of (isSelected)
- Instance 2.19.7
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)@IBOutlet weak var button1: UIButton!
@IBOutlet weak var button2: UIButton!
@IBOutlet weak var button3: UIButton!
override func viewDidLoad(a) {
// The first button is selected by default
button1.isSelected = true
// Force unpacking to avoid having to deal with optional types later
let buttons = [button1, button2, button3].map{$0! }
// Create an observable sequence that sends the last button clicked (i.e. the one we need selected)
let selectedButton = Observable.from(
buttons.map { button in button.rx.tap.map { button } }
).merge()
// subscribe to the selectedButton for each button, binding the isSelected property based on whether it is the currently selectedButton
for button in buttons {
selectedButton.map{$0 == button }
.bind(to: button.rx.isSelected)
.disposed(by: disposeBag)
}
}
}
Copy the code
2.20 Rx extension of UISwitch and UISegmentedControl
2.20.1 UISwitch
- Instance 2.20.1
When the switch status changes, output the current value.
switch1.rx.isOn.asObservable()
.subscribe(onNext: {
print("Current switching status:\ [$0)")
})
.disposed(by: disposeBag)
Copy the code
switch1.rx.isOn
.bind(to: button1.rx.isEnabled)
.disposed(by: disposeBag)
Copy the code
2.20.2 UISegmentedControl
- Instance 2.20.2
When the UISegmentedControl selected item changes, output the current selected item index value.
segmented.rx.selectedSegmentIndex.asObservable()
.subscribe(onNext: {
print("Current item:\ [$0)")
})
.disposed(by: disposeBag)
Copy the code
When the segmentedControl option is changed, the imageView automatically displays the corresponding image.
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
// segment select control
@IBOutlet weak var segmented: UISegmentedControl!
// Image display control
@IBOutlet weak var imageView: UIImageView!
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
// Create an observable sequence of images that you currently want to display
let showImageObservable: Observable<UIImage> =
segmented.rx.selectedSegmentIndex.asObservable().map {
let images = ["js.png"."php.png"."react.png"]
return UIImage(named: images[$0])!
}
// Bind the image to the imageView
showImageObservable.bind(to: imageView.rx.image)
.disposed(by: disposeBag)
}
}
Copy the code
2.21 Rx extension of UIActivityIndicatorView and UIApplication
2.21.1 UIActivityIndicatorView
- Instance 2.21.1
By switching we can control whether the activity indicator shows rotation
mySwitch.rx.value
.bind(to: activityIndicator.rx.isAnimating)
.disposed(by: disposeBag)
Copy the code
2.21.2 UIApplication
- Example 2.21.2 When the switch is turned on, there is a daisy-shaped networking indicator on the top status bar. When the switch is off, the networking indicator disappears.
mySwitch.rx.value
.bind(to: UIApplication.shared.rx.isNetworkActivityIndicatorVisible)
.disposed(by: disposeBag)
Copy the code
2.22 Rx extension of UISlider and UIStepper
2.22.1 UISlider
- Example 2.22.1 Drag the slider and output the current value of slider in real time in the console
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var slider: UISlider!
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
slider.rx.value.asObservable()
.subscribe(onNext: {
print("Current value is:\ [$0)")
})
.disposed(by: disposeBag)
}
}
Copy the code
2.22.2 UIStepper
- Example 2.22.2 Output the current value in real time in the console when the Stepper value changes.
stepper.rx.value.asObservable()
.subscribe(onNext: {
print("Current value is:\ [$0)")
})
.disposed(by: disposeBag)
Copy the code
Use the slider to control the stepper’s step size.
slider.rx.value
.map{ Double($0)}// Since slider value is of type Float and Stepper stepValue is of type Double, a conversion is required
.bind(to: stepper.rx.stepValue)
.disposed(by: disposeBag)
Copy the code
2.23 Bidirectional Binding: <->
- For UI use, basically all bindings are one-way. But sometimes we need to implement bidirectional binding. For example, the value of a property of a control
ViewModel
In a certainSubject
Property for bidirectional binding:
- This way, when a value changes in the ViewModel, it can be synchronized to the control.
- If you change the control value, the ViewModel value will change as well.
2.23.1 Simple two-way binding
- Instance 2.23.1
(1) At the top of the page is a text input box for filling in the user name. It is bidirectionally bound to the USERNAME property in the VM. (2) The text label below will display the corresponding user information according to the user name. (Only hangge shows the administrator, all others are visitors)
(1) First define a VM, the code is as follows:
import RxSwift
struct UserViewModel {
/ / user name
let username = Variable("guest")
// User information
lazy var userinfo = {
return self.username.asObservable()
.map{$0= ="hangge" ? "You are the administrator." : "You are a visitor." }
.share(replay: 1()})}Copy the code
(2) The page code is as follows (the highlighted part is the bidirectional binding between TextField and VM) :
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var label: UILabel!
var userVM = UserViewModel(a)let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
// Bind the user name bidirectionally to textField
userVM.username.asObservable().bind(to: textField.rx.text).disposed(by: disposeBag)
textField.rx.text.orEmpty.bind(to: userVM.username).disposed(by: disposeBag)
// Bind user information to the label
userVM.userinfo.bind(to: label.rx.text).disposed(by: disposeBag)
}
}
Copy the code
2.23.2 Customizing the Bidirectional Binding Operator
- RxSwift’s built-in bidirectional binding operator
(1) If bidirectional binding is often performed, it is better to define a custom operator for easy use. (2) Fortunately, there is one already in the RxSwift project folder (operators.swift) that we can copy into our project and use. Of course, we can refer to it if we want to write some other bidirectional binding operators ourselves.
- Example 2.23.2 Bidirectional binding operator is: <->. If we modify example 2.23.1 above, we can see that the code is much leaner
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var label: UILabel!
var userVM = UserViewModel(a)let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
// Bind the user name bidirectionally to textField
_ = self.textField.rx.textInput <-> self.userVM.username
// Bind user information to the label
userVM.userinfo.bind(to: label.rx.text).disposed(by: disposeBag)
}
}
Copy the code
2.24 UIGestureRecognizer
- Example 2.24.1 When a finger slides upward on the interface, a prompt box is displayed and the coordinates of the starting point of the slide are displayed.
(1) The first response callback writing method
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
super.viewDidLoad()
// Add a swipe up gesture
let swipe = UISwipeGestureRecognizer()
swipe.direction = .up
self.view.addGestureRecognizer(swipe)
// Gesture response
swipe.rx.event
.subscribe(onNext: { [weak self] recognizer in
// This point is the starting point of the slide
let point = recognizer.location(in: recognizer.view)
self? .showAlert(title:"Swipe up", message: "\(point.x) \(point.y)")
})
.disposed(by: disposeBag)
}
// Displays a message prompt box
func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Sure", style: .cancel))
self.present(alert, animated: true)}}Copy the code
(2) The second way to write the response callback
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
super.viewDidLoad()
// Add a swipe up gesture
let swipe = UISwipeGestureRecognizer()
swipe.direction = .up
self.view.addGestureRecognizer(swipe)
// Gesture response
swipe.rx.event
.bind { [weak self] recognizer in
// This point is the starting point of the slide
let point = recognizer.location(in: recognizer.view)
self? .showAlert(title:"Swipe up", message: "\(point.x) \(point.y)")
}
.disposed(by: disposeBag)
}
// Displays a message prompt box
func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Sure", style: .cancel))
self.present(alert, animated: true)}}Copy the code
- Example 2.24.2 enables the input box to lose focus by clicking any position on the page
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
super.viewDidLoad()
// Add a click gesture
let tapBackground = UITapGestureRecognizer()
view.addGestureRecognizer(tapBackground)
// Click anywhere on the page and the input box loses focus
tapBackground.rx.event
.subscribe(onNext: { [weak self] _ in
self? .view.endEditing(true)
})
.disposed(by: disposeBag)
}
}
Copy the code
2.25 UIDatePicker
2.25.1 Date selection response
- Example 2.25.1 When the time in the date picker changes, format the time to display in the label
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var datePicker: UIDatePicker!
@IBOutlet weak var label: UILabel!
// Date formatter
lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "Yyyy year MM month DD day HH: MM"
return formatter
}()
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
datePicker.rx.date
.map{[weak self] in
"Current selected time:" + self! .dateFormatter.string(from: $0)
}
.bind(to: label.rx.text)
.disposed(by: disposeBag)
}
}
Copy the code
2.25.2 Countdown function
- Instance 2.25.2
Select the time to count down through the datepicker at the top, and click the “Start” button to start counting down. Both datepicker and button are unavailable during the countdown. And the button title changes to show the remaining countdown time.
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
// Countdown time selection control
var ctimer:UIDatePicker!
// Start button
var btnstart:UIButton!
// Remaining time (must be an integer multiple of 60, for example, set to 100, the value automatically changes to 60)
let leftTime = Variable(TimeInterval(180))
// Whether the current countdown is over
let countDownStopped = Variable(true)
let disposeBag = DisposeBag(a)override func viewDidLoad(a) {
super.viewDidLoad()
// Initialize datepicker
ctimer = UIDatePicker(frame:CGRect(x:0, y:80, width:320, height:200))
ctimer.datePickerMode = UIDatePickerMode.countDownTimer
self.view.addSubview(ctimer)
// Initialize 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)
// Bind with datepicker bidirectional
DispatchQueue.main.async{// dispatchqueue.main. async is added to solve the problem that the first dial does not trigger a value change event
_ = self.ctimer.rx.countDownDuration <-> self.leftTime //<-> is a custom bidirectional binding symbol
}
// Bind the button title
Observable.combineLatest(leftTime.asObservable(), countDownStopped.asObservable()) {
leftTimeValue, countDownStoppedValue in
// Set the title of the button based on the current state
if countDownStoppedValue {
return "Start"
}else{
return "The countdown is on, and\(Int(leftTimeValue)Seconds..."
}
}.bind(to: btnstart.rx.title())
.disposed(by: disposeBag)
// Bind button and datepicker states (button and time selector components are not available during counting back)
countDownStopped.asDriver().drive(ctimer.rx.isEnabled).disposed(by: disposeBag)
countDownStopped.asDriver().drive(btnstart.rx.isEnabled).disposed(by: disposeBag)
// Button click response
btnstart.rx.tap
.bind { [weak self] in
self? .startClicked() } .disposed(by: disposeBag) }// Start the countdown
func startClicked(a) {
// Start the countdown
self.countDownStopped.value = false
// Create a timer
Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.takeUntil(countDownStopped.asObservable().filter{$0 }) // Stop the timer when the countdown ends
.subscribe { event in
// Each time the remaining time minus 1
self.leftTime.value -= 1
If the remaining time is less than or equal to 0
if(self.leftTime.value == 0) {
print("The countdown is over!)
// End the countdown
self.countDownStopped.value = true
// Rework time
self.leftTime.value = 180
}
}.disposed(by: disposeBag)
}
}
Copy the code