如何:UITextField 等待用户停止输入

huangapple go评论65阅读模式
英文:

How-to: UITextField wait until user stopped typing

问题

我正在尝试找到处理这个情况的最佳方法。我目前正在等待用户停止输入后再运行一些代码(延迟3秒),我打算使用textFieldDidEndEditing,但正如我们所知,这仅在用户返回时执行,而不是在他们停止输入时执行。

目前我使用一个@IBAction textFieldEditingChanged,但这会在每输入一个字符/数字时执行代码。我尝试使用一个像shouldExecuteCode: Bool这样的变量,但由于我在解析XML,响应几乎是立即的,代码仍然会执行多次。

我考虑使用某种OperationQueue与一个操作或无并发性。

有没有人对此有好的想法?

当前实现

@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)
            }
        }
    }
}

潜在实现

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)
英文:

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)

答案1

得分: 1

以下是使用Combine框架的示例。这设置了两个简单的UITextFields,并设置了一个接收两者更改的通知,直到两个文本字段都没有发生更改3秒为止。

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!)")
            }
    }
}

您可以在文本字段中键入任何内容,切换到另一个文本字段并键入内容。只有在两个文本字段都停止输入3秒后,sink块才会运行。

英文:

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.

huangapple
  • 本文由 发表于 2023年5月25日 04:24:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/76327175.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定