I'm trying to find the best way to handle this. I'm currently looking to wait until user stops typing (let's say delay 3s) before running some code. Initially I was going to use textFieldDidEndEditing
but as we know this only executes when user returns, rather than when they stop typing.
Currently I use an @IBAction textFieldEditingChanged
but this will execute the code with each entered character/number. I tried to use a variable like shouldExecuteCode: Bool
but since I'm parsing an XML, the response is almost immediate and the code still get executed several times.
I was thinking to use some sort of OperationQueue
with one operation or no concurrency.
Anyone got a good idea for this?
Current Implementation
@IBAction func textFieldEditingChanged(_ sender: CurrencyTextField) {
self.viewModel.currentTextFieldIdentifier = sender.textFieldIdentifier
DispatchQueue.main.asyncAfter(deadline: .now() + viewModel.calculationDelay) {
if let text = sender.text,
let value = String.format(text) {
switch self.viewModel.currentTextFieldIdentifier {
case .fromCurrency:
self.calculateExchange(value: value,
valueCurrency: self.fromCurrencyTextField.currency,
outputCurrency: self.toCurrencyTextField.currency)
case .toCurrency:
self.calculateExchange(value: value,
valueCurrency: self.toCurrencyTextField.currency,
outputCurrency: self.fromCurrencyTextField.currency)
}
}
}
}
Potential Implementation
NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification, object: sender)
.map({ ($0.object as? UITextField)?.text ?? "" })
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
.sink { [weak self] text in
guard let self = self else { return }
if let value = String.format(text) {
print(value)
switch self.viewModel.currencyTextFieldIdentifier {
case .fromCurrency:
self.calculateExchange(value: value,
valueCurrency: self.fromCurrencyTextField.currency,
outputCurrency: self.toCurrencyTextField.currency)
case .toCurrency:
self.calculateExchange(value: value,
valueCurrency: self.toCurrencyTextField.currency,
outputCurrency: self.fromCurrencyTextField.currency)
}
}
}
.store(in: &subscriptions)
Here is an example using the Combine framework. This sets up two simple UITextFields and sets up one sink for both such that you don't get any notifications until no change has occurred in either text field for 3 seconds.
class ViewController: UIViewController {
var sub: AnyCancellable!
override func viewDidLoad() {
super.viewDidLoad()
let tf1 = UITextField(frame: CGRect(x: 70, y: 70, width: 150, height: 44))
tf1.borderStyle = .roundedRect
self.view.addSubview(tf1)
let tf2 = UITextField(frame: CGRect(x: 70, y: 120, width: 150, height: 44))
tf2.borderStyle = .roundedRect
self.view.addSubview(tf2)
sub = NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification, object: nil)
.debounce(for: .seconds(3), scheduler: RunLoop.main)
.sink { _ in
print("\(Date()): tf1: \(tf1.text!), tf2: \(tf2.text!)")
}
}
}
You can type all you want into a text field, switch to the other and type. The sink
block will not run until you've stopped typing in both text fields for 3 seconds.