Basic UI control extension for 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.

Download information: Download address

1. UILabel RX extension

1.2 Binding data to the TEXT property (plain text)

  • Example 1.2
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController {let disposeBag = disposeBag () override func viewDidLoad() {// Create text label let label = UILabel(frame:CGRect(x:20, y:40, width:300, Timer = Observable<Int>. Interval (0.1, scheduler: 1) self.view.addSubView (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) } }Copy the code

1.3 Binding data to the attributedText attribute (rich text)

  • Example 1.3
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController {let disposeBag = disposeBag () override func viewDidLoad() {// Create text label let label = UILabel(frame:CGRect(x:20, y:40, width:300, Timer = Observable<Int>. Interval (0.1, scheduler: 1) self.view.addSubView (label) Mainscheduler.instance) // Formats the past time into the desired string and binds it to the label timer.map(formatTimeInterval).bind(to: Label.rx.attributedtext).disposed(by: disposeBag)} // Convert the number into the corresponding rich text func formatTimeInterval(ms: NSInteger) - > NSMutableAttributedString {let string = string (format: "% 0.2 d: % d 0.2. % 0.1 d", the arguments: [(ms / 600) %, 600 (ms % 600) / 10, 10] ms %) / / rich text set let attributeString = NSMutableAttributedString (string: From the text string) / / 0 6 characters HelveticaNeue font - Bold, 16 attributeString. AddAttribute (NSAttributedStringKey. The 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. UITextField, UITextView Rx extension

2.1 Monitor the changes of the contents of a single textField (same with textView)

  • Example 2.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 () override func viewDidLoad() {// Create text input box let textField = UITextField(frame: CGRect(x:10, y:80, width:200, Height: 30)) textField borderStyle. = UITextBorderStyle roundedRect self. The addSubview (textField) / / when the content of the text box changes, Content will be output to the console textField. Rx. Text. OrEmpty. AsObservable (). The subscribe (onNext: {print (" you entered 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, the content of the output to the console textField. Rx. Text. OrEmpty. Changed. The subscribe (onNext: {print (" you entered is: \ ($0) ")}), disposed (by: disposeBag)Copy the code

2.2 Binding content to other controls

  • Example 2.12

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 () override func viewDidLoad() {// Create text input box let inputField = UITextField(frame: CGRect(x:10, y:80, width:200, Height: 30)) inputField. BorderStyle. = UITextBorderStyle roundedRect self. The addSubview (inputField)/output/create a text box outputField = UITextField(frame: CGRect(x:10, y:150, width:200, Height: 30)) outputField borderStyle. = UITextBorderStyle roundedRect self. The addSubview (outputField) / / create text labels let label  = UILabel(frame:CGRect(x:20, y:190, width:300, Self.view.addsubview (label) // Create a 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 content of the text box to change the input = inputField. Rx. Text. OrEmpty. AsDriver () / / will be converted to regular sequence Driver.throttle (0.3) // In the mainline prompt, if the value changes many times, take the last time // Bind the content to another input box input.drive(outputfield.rx.text). Disposed (by: Input.map {" Current word count: \($0.count)"}. Drive (label.rx.text).disposed(by: Input.map {$0.count > 5}. Drive (button.rx.isenabled). Disposed (by: disposeBag)}}Copy the code

2.3 Monitor the content changes of multiple TextFields at the same time (same with textView)

  • Example 2.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 "you input number is: \(textValue1)-\(textValue2)" } .map { $0 } .bind(to: label.rx.text) .disposed(by: disposeBag) } }Copy the code

2.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 against 2.4.1
TextField. Rx. ControlEvent ([editingDidBegin]) / / state can be combined. The asObservable (). The subscribe (onNext: {_ in print (" start editing content!" ) }).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:

  1. If the current focus is on the user name input box, press the Return key to switch the focus to the password input box.
  2. If the current focus is in the password input box, the focus is automatically removed when you press return.
  • Instance 2.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 () // Press return in the user name input box username.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: { [weak self] (_) in self? .password.becomeFirstResponder() }).disposed(by: ControlEvent (.editingDidEndOnExit).subscribe(onNext: { [weak self] (_) in self? .password.resignFirstResponder() }).disposed(by: disposeBag) } }Copy the code

2.5 UITextView unique method

  • UITextView also encapsulates the following delegate callback methods:

DidChange: didChangeSelection: the selected part has changed

  • Example 2.5
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var textView: UITextView! Override func viewDidLoad () {/ / edit response textView. Rx. DidBeginEditing. Subscribe (onNext: {print (" start editing ")}), disposed (by: DisposeBag) / / end edit response textView. Rx. DidEndEditing. Subscribe (onNext: {print (" end edit ")}), disposed (by: Prompt (textView.rx.didChange. Subscribe (onNext: {print(" Content changed ")}).subscribe(by: Responses are textView disposeBag) / / selected. The rx. DidChangeSelection. Subscribe (onNext: {print (change "selected")}), disposed (by: disposeBag) } }Copy the code

3. Rx extension of UIButton and UIBarButtonItem

3.1 Button click response

  • Example 3.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 (" Button clicked ")}). Prompt (by: disposeBag)} // Show prompt func showMessage(_ text: String) { let alertController = UIAlertController(title: text, message: nil, preferredStyle: .alert) let cancelAction = UIAlertAction(title: "ok ", 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

Rx.tap. bind {[weak self] in self? .showMessage(" Button clicked ")}. Disposed (by: disposeBag)Copy the code

3.2 Binding of button title

  • Example 3.2
Timer = Observable<Int>. Interval (1, scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler; Map {" count \($0)"}. Bind (to: button.rx.title(for: .normal))// rx.title is the wrapper of setTitle(_:for:). .disposed(by: disposeBag)Copy the code

3.3 Binding of button’s attributedTitle

  • Example 3.3
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var button: UIButton! Override func viewDidLoad() {// Create a timer that sends an index every 1 second. Mainscheduler.instance) // Format the past time into the desired string and bind it to the button timer.map(formatTimeInterval).bind(to: Button. The rx. AttributedTitle ()) / / rx. AttributedTitle setAttributedTitle (_ : controlState encapsulation:). Disposed (by: disposeBag)} // Convert the number into the corresponding rich text func formatTimeInterval(ms: NSInteger) - > NSMutableAttributedString {let string = string (format: "% 0.2 d: % d 0.2. % 0.1 d", the arguments: [(ms / 600) %, 600 (ms % 600) / 10, 10] ms %) / / rich text set let attributeString = NSMutableAttributedString (string: From the text string) / / 0 6 characters HelveticaNeue font - Bold, 16 attributeString. AddAttribute (NSAttributedStringKey. The 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

3.4 Binding button ICONS (Image

  • Example 3.4
Timer = Observable<Int>. Interval (1, scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler; Mainscheduler.instance) // Select the corresponding button icon based on the index number and bind it to button timer.map({let name = $0%2 == 0? "back" : "forward" return UIImage(named: name)! Bind (to: button.rx.image())// rx.image is a wrapper for setImage(_:for:). .disposed(by: disposeBag)Copy the code

3.5 binding of a button backgroundImage

  • Example 3.5
Timer = Observable<Int>. Interval (1, scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler: scheduler; Mainscheduler.instance) // Select the corresponding button background image based on the index number and bind it to button timer.map{UIImage(named: "\($0%2)")! }. Bind the to: button. The rx. BackgroundImage ()) / / rx backgroundImage setBackgroundImage (_ : for packaging:). .disposed(by: disposeBag)Copy the code

3.6 Button Binding (isEnabled

  • Example 3.6
switch1.rx.isOn
    .bind(to: button1.rx.isEnabled)
    .disposed(by: disposeBag)
Copy the code

3.7 Button Whether to select the binding (isSelected)

  • Example 3.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; To avoid dealing with optional types later let buttons = [button1, button2, button3].map {$0! } // Create an observable sequence, Let selectedButton = Observable from(buttons.map {button in button.rx.tap.map {button}) let selectedButton = Observable from(buttons.map {button in button.rx.tap.map {button}) }).merge() // Subscribe to selectedButton for each button, For buttons in buttons {selectedbutton. map {$0 == button}. Bind (to: button.rx.isSelected) .disposed(by: disposeBag) } } }Copy the code

4. Rx extension of UISwitch, UISegmentedControl

4.1 UISwitch

  • Example 4.1

When the switch status changes, output the current value.

Switch1. Rx. IsOn. AsObservable (). The subscribe (onNext: {print (current switch state: \ "($0)")}), disposed (by: disposeBag)Copy the code
switch1.rx.isOn
    .bind(to: button1.rx.isEnabled)
    .disposed(by: disposeBag)
Copy the code

4.2 UISegmentedControl

  • Example 4.2

When the UISegmentedControl selected item changes, output the current selected item index value.

Segmented. Rx. SelectedSegmentIndex. AsObservable (). The subscribe (onNext: {print (the 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 {// segmented select control @iboutlet weak var segmented: UISegmentedControl! @ibOutlet weak var imageView: UIImageView! Func viewDidLoad() {// Create an observable sequence of images that need to be displayed. Observable<UIImage> = segmented.rx.selectedSegmentIndex.asObservable().map { let images = ["js.png", "php.png", "react.png"] return UIImage(named: images[$0])! Imageobservable. bind(to: imageView.rx.image).disposed(by: disposeBag)}}Copy the code

5. Rx extension of UIActivityIndicatorView and UIApplication

5.1 UIActivityIndicatorView

  • Example 5.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

5.2 UIApplication

  • Example 5.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

6. Rx extension of UISlider and UIStepper

6.1 UISlider

  • Example 6.1 Output the current value of slider in real time from the console while dragging the 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 (the current value for: \ "($0)")}) disposed (by: disposeBag)}}Copy the code

6.2 UIStepper

  • Example 6.2 Output the current value in real time in the console when the Stepper value changes.
Stepper. Rx. Value. AsObservable (). The 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)} // Because slider.rx.value. map{Double($0)} // Because slider.rx.value. map{Double($0)} // Because slider.rx.value. map{Double($0)} // Because slider.rx.value. map{Double($0)} stepper.rx.stepValue) .disposed(by: disposeBag)Copy the code

7. 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 ViewModelIn a certainSubjectProperty for bidirectional binding:
  1. This way, when a value changes in the ViewModel, it can be synchronized to the control.
  2. If you change the control value, the ViewModel value will change as well.

7.1 Simple bidirectional Binding

  • Example 7.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 {// username let username = Variable("guest") // user information lazy var userinfo = {return self.username.asObservable() .map{ $0 == "hangge" ? "You are an 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() let disposeBag = disposeBag () override func viewDidLoad() userVM.username.asObservable().bind(to: textField.rx.text).disposed(by: disposeBag) textField.rx.text.orEmpty.bind(to: Uservm.username).prompt (by: disposeBag) // Bind the user information to the label Uservm.userinfo.bind (to: label.rx.text).prompt (by: disposeBag) } }Copy the code

7.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 7.2 Bidirectional binding operator is: <->. If we modify example 7.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() let disposeBag = disposeBag () override func viewDidLoad() {// Bind user name to textField Self. TextField. Rx. (< - > self. UserVM. Username / / to bind the user information on the label userVM. The userinfo. Bind (to: label.rx.text).disposed(by: disposeBag) } }Copy the code

8. UIGestureRecognizer

  • Example 8.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 () override func viewDidLoad() {super.viewdidLoad () // Add a let swipe gesture = UISwipeGestureRecognizer () swipe. Direction =. Up the self. The addGestureRecognizer (swipe) / / response swipe gesture. The rx. The event .subscribe(onNext: {[weak self] recognizer in // This point is the start of the slide let point = recognizer.location(in: recognizer.view) self? Prompt (title: "Swipe up ", message: "\(point.x) \(point.y)")}). Disposed (by: Func showAlert(title: String, message: String) {let alert = UIAlertController(title: Title, message: message, preferredStyle:.alert) alert.addAction(UIAlertAction(title: "ok ", 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 () override func viewDidLoad() {super.viewdidLoad () // Add a let swipe gesture = UISwipeGestureRecognizer () swipe. Direction =. Up the self. The addGestureRecognizer (swipe) / / response swipe gesture. The rx. Event. The bind { [weak self] recognizer in // This point is the start of the slide let point = recognizer.location(in: recognizer.view) self? Prompt (title: "Swipe up ", message: "\(point.x) \(point.y)")}. Func showAlert(title: String, message: String) {let alert = UIAlertController(title: Title, message: message, preferredStyle:.alert) alert.addAction(UIAlertAction(title: "ok ", style: .cancel)) self.present(alert, animated: true) } }Copy the code
  • Example 8.2 makes the input field lose focus by clicking anywhere on the page
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController {let disposeBag = disposeBag () override func viewDidLoad() {super.viewdidLoad () // add a click gesture let TapBackground = UITapGestureRecognizer () the addGestureRecognizer (tapBackground) / / page click any place, Subscribe (onNext: {[weak self] _ in self? .view.endEditing(true) }) .disposed(by: disposeBag) } }Copy the code

9. UIDatePicker

9.1 Date selection response

  • Example 9.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! // dateFormatter lazy var dateFormatter: DateFormatter = {let formatter = DateFormatter() formatter. DateFormat = "YYYY MM dd HH: MM" return formatter}() let Override func viewDidLoad() {datepicker.rx.date.map {[weak self] in "" + self! .dateFormatter.string(from: $0) } .bind(to: label.rx.text) .disposed(by: disposeBag) } }Copy the code

9.2 Countdown Function

  • Example 9.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 select control var ctimer:UIDatePicker! Var btnStart :UIButton! // The remaining time (must be an integer multiple of 60, such as 100, The value automatically changes to 60) let leftTime = Variable(TimeInterval(180)) // Whether the current countdown is over let countDownStopped = Variable(true) let disposeBag = Override func viewDidLoad() {super.viewdidLoad () // Initialize datepicker ctimer = UIDatePicker(frame:CGRect(x:0, y:80, width:320, Height: 200)) ctimer datePickerMode = UIDatePickerMode. CountDownTimer self. The addSubview initialization (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) sell.view.addSubView (btnstart) dispatchQueue.main.async {// add DispatchQueue. Main. Async is the first time in order to solve the toggle dial does not trigger a value change events question _ = self. Ctimer. Rx. CountDownDuration < - > self. LeftTime / / < - > Observable.combineLatest(lefttime.asObservable (), countDownStopped.asObservable()) { leftTimeValue, If countDownStoppedValue {return "start"}else{return "countdown starts, And \(Int(leftTimeValue)) seconds..." } }.bind(to: btnstart.rx.title()) .disposed(by: // Bind button and datepicker states. Button and time selection component is not available) countDownStopped. AsDriver () drive (ctimer. Rx. IsEnabled) disposed (by: disposeBag) countDownStopped.asDriver().drive(btnstart.rx.isEnabled).disposed(by: Btnstart. rx.tap. bind {[weak self] in self? .startClicked() } .disposed(by: DisposeBag)} / / start the countdown func startClicked () {/ / start the countdown self. CountDownStopped. Value = false / / create a timer Observable<Int>.interval(1, scheduler: MainScheduler. Instance). TakeUntil (countDownStopped asObservable (). The filter {$0}) / / at the end of the countdown to stop the timer. The subscribe {event in Value -= 1 if(self.lefttime. value == 0) {print(" Count down!" ) / / end of the countdown to the self. CountDownStopped. Value = true / / remakes time self leftTime. Value = 180}}. Disposed (by: disposeBag)}}Copy the code

Since the | address

Download information: Download address