Rxswift/Rxcocoa: Prevent Uitextfield from Having More Than ... Characters

RxSwift/RxCocoa: prevent UITextField from having more than ... characters

Sure:

textField.rx.controlEvent(.editingChanged).subscribe(onNext: { [unowned self] in
if let text = self.textField.text {
self.textField.text = String(text.prefix(40))
}
}).disposed(by: disposeBag)

In this example, the textfield is limited to 40 characters.

Edit:

Keeping the previous value when the limit is reached.

textField.rx.text.orEmpty
.scan("") { (previous, new) -> String in
if new.count > 40 {
return previous ?? String(new.prefix(40))
} else {
return new
}
}
.subscribe(textField.rx.text)
.disposed(by: disposeBag)

This can probably be adapted to respect other rules...

Please note however that when reaching the character limit, your cursor will jump to the end of the textField.

Why RxCocoa didn't provide subscribe for UILabel's text change?

In essence, the text method is a wrapper around the UIControl's addTarget(:action:for:) method. It turns that method into something that is Observable.

The UILabel type isn't derived from UIControl and so doesn't have an addTarget(:action:for:) method. You can't attach an @IBAction to a UILabel in normal UIKit, so you can't attach an Rx ControlProperty to a UILabel either.

And just like in UIKit, you don't need to observe changes to a UILabel because the only way it changes is if your code changed it. Just observe the thing that triggered the label to change instead.


It will help to understand if you look at the code... The text method is just a rename of the value method:

public var text: ControlProperty<String?> {
value
}

and the value method is a ControlProperty that monitors the "default events".

    public var value: ControlProperty<String?> {
return base.rx.controlPropertyWithDefaultEvents(

Looking at controlPropertyWithDefaultEvents, we can find out what the default events are:

internal func controlPropertyWithDefaultEvents<T>(
editingEvents: UIControl.Event = [.allEditingEvents, .valueChanged],
getter: @escaping (Base) -> T,
setter: @escaping (Base, T) -> Void
) -> ControlProperty<T>

Disabling a button based on value in a UITextField only works once (RxSwift)

I think there is a little misunderstanding about how ControlProperty trait behaves. Let's take a look at specific behavior which is Programmatic value changes won't be reported

This Observable observationTextField.rx.text after subscription will not emit event for both:

self.observationTextField.rx.text.onNext("")

or

self.observationTextField.text = ""

I have 2 suggestion for your code:

1) Do the job manually:

viewModel.addObservation.executionObservables
.subscribe({ [unowned self] _ in
self.observationTextField = ""
self.addObservationButton.isEnabled = false
})
.disposed(by: disposeBag)

2) Add one more Observable and subscription:

//a
viewModel.addObservation.executionObservables
.map { _ in return "" }
.bind(to: observationTextField.rx.text)
.disposed(by: disposeBag)

viewModel.addObservation.executionObservables
.map { _ in return false }
.bind(to: addObservationButton.rx.isEnabled)
.disposed(by: disposeBag)
//b
let executionObservables = viewModel.addObservation
.executionObservables
.share()

executionObservables
.map { _ in return "" }
.bind(to: observationTextField.rx.text)
.disposed(by: disposeBag)

executionObservables
.map { _ in return false }
.bind(to: addObservationButton.rx.isEnabled)
.disposed(by: disposeBag)

Not sure how Action is implemented, to prevent job done twice maybe you have to share resources.

RxSwift With UiTextField

Based on your description of the problem, it seems like the debounce operator is more suitable than throttle. debounce only emits an element if a certain time, in which nothing has been emitted, has passed, whereas throttle ensures that elements are emitted at least a certain time interval apart.

I can confirm that your code using throttle is working as I'd expect - if I type very quickly, the "Response from API" messages appear roughly every 1 second. If I type very slowly, slower than 1 keystroke per second, then the messages come whenever I press a key. In other words, whenever there is an editing changed event, throttle checks to see if there was a previous one less than 1 second ago. If there is, ignore this new one.

If you use debounce however (the same code, just replace throttle with debounce), then after each keystroke, it would wait for 1 second to see if the text field is going to change again. It would only emit the editing changed event if it has waited and there is no more editing changed events. So if you keep typing at a pace higher than 1 keystroke per second, no elements will ever be emitted by the observable. This seems to be what you want.

You can compare these two operators on RxMarbles (they are called slightly different names there):

  • debounceTime
  • throttleTime

RxSwift Error: Property 'text' requires that 'UITextField' inherit from 'UILabel'

Use: .debounce(.milliseconds(300), scheduler: MainScheduler.instance) instead. The debounce(_:scheduler:) that takes a Double has been removed.

Also, you are doing too much in your subscribe. You are better off if you break the subscribe up into independent units:

let searchText = searchTextField.rx.text.asObservable().skip(3)
.debounce(.milliseconds(300), scheduler: MainScheduler.instance)
.compactMap { $0 }

searchText
.map { $0.isEmpty }
.bind(to: clearButton.rx.isHidden)
.disposed(by: disposeBag)

searchText
.bind { [presenter] in
presenter?.getAreaCodeList(text: $0)
}
.disposed(by: disposeBag)

RxSwift Custom DataType Convertion without Binders

This is very poor practice and really should be avoided.

That said, if you really need it while refactoring to full RxSwift, you can bind to a BehaviorRelay and then use .value to get the current value out.



Related Topics



Leave a reply



Submit