Search code examples
iosobjective-cafnetworkinguisearchbarnsoperationqueue

How do I set a delay before performing an action in UISearchDisplayDelegate?


I have a search display controller which hits an API endpoint. My current code will make a request to the API endpoint on every single char. What I want to do it make a request only when the user has stop typing for 500ms.

Here is the code:

In the UISearchDisplayDelegate

Note: searchQueue is an NSOperationQueue object.

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
    [self.searchQueue cancelAllOperations];

    [self.searchQueue addOperationWithBlock:^(){
        [self.AFRequestManager.operationQueue cancelAllOperations];

        NSString *access_token = [[FBSDKAccessToken currentAccessToken] tokenString];

        NSDictionary *params = @{@"name": searchString, @"access_token": access_token };

        NSString *getUrl = [baseUrl stringByAppendingString:@"api/users/search"];
        [self.AFRequestManager GET:getUrl parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
            self.searchedUsers = responseObject;
            [self.searchDisplayController.searchResultsTableView reloadData];

        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"Error: %@", error);
        }];
    }];

    return NO;
}

This delegate method gets called for every character that the user typed in and I would like to wait until the user finishes specifying the name.

I have tried using NSTimer but it's messy. I can definitely pass the searchString to userInfo. However, once I invalidate the NSTimer, it cannot be used again.

I have tried using a dispatch_after but actually that does not work because every time the user enter a char, the search is delayed but it is still making a request for every single character the user enters.

I did not want to overcomplicate but I feel like it should be super easy and I'm missing something.


Solution

  • What about calling performSelector:withObject:afterDelay:? It's available to all NSObjects so you could call it from your view controller. The only thing you might have to worry about is not taking on every single event and queuing up 2 minutes worth of delays:

    - (void)performSelector:(SEL)aSelector
                 withObject:(id)anArgument
                 afterDelay:(NSTimeInterval)delay
    

    Example (note: I made up the variable names for the purpose of demonstration):

    [self performSelector:@selector(callServer:) withObject:params afterDelay:0.5f];
    

    UPDATE:

    It appears as though the first part of my answer won't suffice. Here are some new steps that should help you derive a solution:

    1) Create one NSTimer property so that you can access it wherever you need to

    2) Move your current logic in searchDisplayController:shouldReloadTableForSearchString: into a new method so that your timer can use it as its selector. For example:

    - (void)callServerWithDictionary:(NSDictionary*)params;

    3) Whenever searchDisplayController:shouldReloadTableForSearchString: is called, if your timer instance is null, instantiate it and set its time interval to be 500ms and its selector to be the method you created in step 1. If the timer is not null, invalidate it and set it equal to a new timer instance with the same 500ms interval and selector as before.

    4) If you are losing the search string, then create a property to hold it and update it every time searchDisplayController:shouldReloadTableForSearchString: is called. Though I believe you should be able to access it with something like searchDisplayController.searchBar.text.