Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SWIFT - run code 1 second after a user stops typing into a textfield

Tags:

ios

swift

In swift, how can I run some code 1 second after a user stops typing into a textfield? There is textFieldDidChange but that will run code immediately after a new character is typed.

like image 209
BlueBoy Avatar asked Aug 05 '17 23:08

BlueBoy


2 Answers

Try with this custom UITextField, you need setup a timer to 1 second every time a user put a character in your UITextField, and invalidate the timer before re-schedule again, I added the action as closure to allow any kind of action, giving full freedom in you implementation

Added Inspectable property for delay customization

import UIKit

@IBDesignable
class AfterOneSecondTextField: UITextField {

    @IBInspectable var delayValue : Double = 1.0
    var timer:Timer?

    var actionClosure : (()->Void)?

    override func awakeFromNib() {
        super.awakeFromNib()
        self.addTarget(self, action: #selector(changedTextFieldValue), for: .editingChanged)
    }

    func changedTextFieldValue(){
        timer?.invalidate()
        //setup timer
        timer = Timer.scheduledTimer(timeInterval: delayValue, target: self, selector: #selector(self.executeAction), userInfo: nil, repeats: false)
    }

    func executeAction(){
        actionClosure?()
    }

    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */

}

Use it

You only need to set the class, and pass a closure to your desired action in my case is debugPrint but you can do whatever you need

class ViewController: UIViewController {
    @IBOutlet weak var tfText: AfterOneSecondTextField!
    override func viewDidLoad() {
        super.viewDidLoad()
        tfText.actionClosure = {
            debugPrint("Test")
        }
    }

This works!, was tested

Hope this helps

like image 185
Reinier Melian Avatar answered Oct 22 '22 09:10

Reinier Melian


What I have done is use a cancelable closure to run code after some delay; that way as the user types the closure keeps getting cancelled, but when they pause long enough or finish the code to search or fetch is actually run after a brief delay.

I used the cancelable closure code from here:

Cancel a timed event in Swift?

Then the code that goes into textFieldDidChange looks like:

 fileprivate var delayedSearchClosure : dispatch_cancelable_closure?
open func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

    if let searchClosure = delayedSearchClosure {
        searchClosure(true)
        delayedSearchClosure = .none
    }

    delayedSearchClosure = delayClosure(0.8) { [weak self] in
        self?.doSearch(searchText)
        self?.delayedSearchClosure = .none
    }
}

To explain what is happening here - in the callback for the textfield as the user types, I first terminate the existing closure created to run at a specific time. Then in the next block of code, it makes a new delayedSearchClosure that will execute 0.8 seconds from now and call "doSearch()", then toss away the closure.

Thus as the user types, closures are being thrown away that no longer matter. Eventually the user will pause or stop typing, at that point the code you want run is actually executed (in this case a search is performed based on the text). This gives you a search form that feels responsive but does not waste time with an added search launched for every character a user types (which can add overhead and start to impact the UI performance).

like image 45
Kendall Helmstetter Gelner Avatar answered Oct 22 '22 09:10

Kendall Helmstetter Gelner