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
Swiftui Navigation on iPad - How to Show Master List
Firebase Query Containing Value
Evaluate Bool Property of Optional Object in If Statement
Vapor Toolbox Broken After Upgrading Swift
How to Add Constraints Programmatically to My Uilabel
!? Strange Double Unwrapped Optional Syntax in For_In []
How to Open Safari Extension Toolbaritem Popover Programmatically
Subclass of Gkgraphnode Costtonode Method Never Getting Called
How to Make Nsattributedstring Codable Compliant
Converting .M4A File to .Aiff Using Audioconverter Swift
How to Get Date and Time to Show a Clock in Uilabel
How to Block Users on Firebase in a Social Media App? for iOS
How to Remove Numbers from a String in Swift
How to Connect Aksequencer to a Akcallbackinstrument