6

I try to implement Search as you type written in Swift. It works already but I need some tuning. I send with each letter typed in

func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {

    let debouncedFindUser = debounce(
        Static.searchDebounceInterval,
        Static.q,
        findUser)

    debouncedFindUser()
}

a request to the backend.

func findUser() -> () {
    SearchService.sharedInstance.getUser(0, numberOfItems: 100,
        searchString: searchUserBar.text.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
        ,
        success: {SearchUserDto -> Void in
            if self.searchUserBar.text != nil {
                self.updateSearch()
            }
        },
        failure: {NSError -> Void in

    })
}

I tried an implementation of Debounce found here How can I debounce a method call?

My Problem:

I want to "restart" a method call if it was triggered within a certain time. So let's exaggerate a little bit.

The request shall only start after search text wasn't typed for 2 seconds. So if I'm a quick typer and type each 0.5 seconds a letter the request should never be triggered except I pause for at least 2 seconds.

Community
  • 1
  • 1
S. Birklin
  • 1,255
  • 1
  • 13
  • 19

3 Answers3

8

You could use an NSTimer. Start or invalidate and restart the timer for 2 seconds in your textDidChange. Have the timer fire the findUser method when it actually pops.

    var debounceTimer: NSTimer?

@IBAction func test() {
    if let timer = debounceTimer {
        timer.invalidate()
    }
    debounceTimer = NSTimer(timeInterval: 2.0, target: self, selector: Selector("getUser"), userInfo: nil, repeats: false)
    NSRunLoop.currentRunLoop().addTimer(debounceTimer!, forMode: "NSDefaultRunLoopMode")
}

func getUser() {
    println("yeah")
}
Jeremy Pope
  • 3,342
  • 1
  • 16
  • 17
  • Why not just use `scheduledTimerWithInterval...` But yeah, `NSTimer` is a much easier way to implement debouncing since you can cancel a timer but can't cancel `dispatch_after` – David Berry Feb 09 '15 at 16:26
1

The problem you're having is that you're debouncing the findUser function before calling it every time. Your debouncing function sets up the initial timer and delay, but since you're calling it each time, the initial timer is always "now". You should debounce only once so the closure can maintain its captured last-executed time.

You want to only call debounce one time and store it as a property, like so:

class MySearchController: ... {
    lazy var debouncedFindUser = debounce(
        Static.searchDebounceInterval,
        Static.q,
        self.findUser)

    ...

    func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
        debouncedFindUser()
    }

    func findUser() {
        ...
    }
}
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • Thank you for the answer Nate Cook. I solved my problem with Jeremy's solution but probably I'm just not able to implement yours.^^ If i initialize the lazy var then i get problem with does not have a member name 'findUser'. It's probably a kind of instance problem... – S. Birklin Feb 09 '15 at 15:30
  • To get around the self undefined variable, you can use `lazy var debouncedFindUser : () -> () = debounce(2.0, dispatch_get_main_queue(), self.findUser)` Explicitly defining the return type seems to resolve it, not sure why. – David Berry Feb 09 '15 at 17:12
0

The only thing u need is to create a Timer

  func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    let text = searchText.trimmingCharacters(in: .whitespacesAndNewlines)

// Create a Timer in this or parent class.
    debounceTimer.invalidate() // just in case this button is tapped multiple times


    // start the timer
    debounceTimer = Timer.scheduledTimer(timeInterval: 0.8, target: self, selector: #selector(self.startSearch(_:)), userInfo: text, repeats: false)
}

func startSearch(_ timer: Timer) {
    if let searchText = timer.userInfo as? String {
        print("Searching: \(searchText)")

        // make your service call here ...
    }
}
Abhishek Bedi
  • 5,205
  • 2
  • 36
  • 62