15

I have the following UISearchbar code:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    NSString* endpoint =[NSString stringWithFormat:@"http://www.someurl/",
                         [searchText stringByReplacingOccurrencesOfString:@" " withString:@"+"]];
    NSURL* url = [NSURL URLWithString:endpoint];
    NSURLRequest* request = [NSURLRequest requestWithURL:url];
    GTMHTTPFetcher* myFetcher = [GTMHTTPFetcher fetcherWithRequest:request];
    [myFetcher beginFetchWithDelegate:self didFinishSelector:@selector(searchResultsFetcher:finishedWithData:error:)];
}

I want to send this request after a pause in input and reset the timer everytime a character is hit. How can I do this?

Sheehan Alam
  • 60,111
  • 124
  • 355
  • 556

5 Answers5

53

It doesn't have to use NSTimer.

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
        {
           [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(request) object:nil];

          //.....

           [self performSelector:@selector(request) withObject:nil afterDelay:yourpausetime];

        }
Sven Tan
  • 1,062
  • 7
  • 15
  • 2
    Much more elegant solution than a NSTimer, I think. Could cause issues depending on the runloop and the user input or request, maybe? Hmm, not anymore so than a timer though – smdvlpr Aug 15 '11 at 03:48
  • Nice! Here's another example with detailed comments: http://benedictcohen.co.uk/blog/archives/157 – ma11hew28 Aug 29 '14 at 15:58
  • not working, the selector calls repeatedly even though we are cancelling (swift 3) – Saif Nov 22 '17 at 11:58
7

In the textDidChange method create an NSTimer, say 2 seconds worth. If the timer already exists, invalidate and recreate the timer. (Untested code:)

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if (myTimer) {
        if ([myTimer isValid]) { [myTimer invalidate]; }
        [myTimer release], myTimer = nil;
    }
    myTimer = [[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(userPaused:) userInfo:nil repeats:NO] retain];
}

When the user stops typing for 2 seconds, -userPaused: will be called and your timer will be automatically invalidated (although not nil). When the user starts typing again a new timer will be setup.

CodaFi
  • 43,043
  • 8
  • 107
  • 153
smdvlpr
  • 1,088
  • 9
  • 22
1

I was able to adapt Sven Tan's answer to my existing code in Swift. In my case, I am sending the string to a method that loads the search results async. Additionally, I am not using the UISearchBar but rather a plain old UITextField.

var currentTempQuery = ""

...

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    if let t = textField.text {
        let s: NSString = t
        let newString = s.stringByReplacingCharactersInRange(range, withString: string).trim()

        NSObject.cancelPreviousPerformRequestsWithTarget(self, selector:#selector(MyViewController.sendSearchRequest(_:)), object: currentTermQuery)

        // Don't replace currentTermQuery until after the cancelPreviousPerformRequestWithTarget call
        currentTermQuery = newString
        performSelector(#selector(MyViewController.sendSearchRequest(_:)), withObject: newString, afterDelay: 1)
    }
    return true
}

Here is the selector that is being called:

func sendSearchRequest(text: String?) {
    // Call async search method here...
}

The way that cancelPreviousPerformRequestsWithTarget works is you need to pass the same target, selector, and object that was passed in the performSelector call in order for the previous request to be cancelled. In my implementation, since I am passing just a string, I need to preserve the current request string between calls so I can reference it to cancel requests.

The result works for typing and deleting characters into my UITextField. Only one search is sent per major search term change.

Like I said, similar to what Sven Tan posted but slightly different usage. Hopefully this helps some people out.

i2097i
  • 764
  • 7
  • 15
0

Excellent code and working perfect for me. I found it here Replacing an NSTimer with performSelector:withObject:afterDelay. Thanks to Ben solution ... all bounties to him i just copy it here to be found easy

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    //because the view can be unloaded we must store all data so that its state can be restored in viewDidLoad
    self.searchQuery = [textField.text stringByReplacingCharactersInRange:range withString:string];

    SEL fetchSearchResults = @selector(fetchSearchResults);

    //cancel the previous search request
    [[self class] cancelPreviousPerformRequestsWithTarget:self selector:fetchSearchResults object:nil];    

    //perform the search in a seconds time. If the user enters addition data then this search will be cancelled by the previous line
    [self performSelector:fetchSearchResults withObject:nil afterDelay:1];

    return YES;
}

- (void)fetchSearchResults
{
    NSLog(@"performing search %@", self.searchQuery);
    ...
}
Nazir
  • 1,945
  • 24
  • 27
-1

Block text input to your search and have an NSTimer call a selector. Show an indicator of some sort so the user doesn't think something is going wrong. When the selector fires give control back to the user to continue typing

[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(targetMethod:) userInfo:nil repeats:NO];
David McGraw
  • 5,157
  • 7
  • 36
  • 36
  • A bunch of NSTimers will go off as the user types. How can I reset the NSTimer when a character is typed? – Sheehan Alam Aug 15 '11 at 03:19
  • I take that back. invalidate only when you want to stop a timer from firing. The sample above has repeats:NO, so it'll fire once and it's done. = – David McGraw Aug 15 '11 at 03:21
  • I'm not exactly sure how to block text input until the timer is done? would you be able to show me an example? – Sheehan Alam Aug 15 '11 at 03:24
  • disable field or add a condition in textDidChange or ... Depends on the user experience you want. Imagine if I am just good and I want to type really fast? Maybe you should just init the server call after I stop typing for a specific time. – David McGraw Aug 15 '11 at 03:34