📜 ⬆️ ⬇️

Responsive Search for UITableView


In this article, I will share with you the approach to implementing a search in the DataSource UITableView when the user quickly types a query, when it is necessary to dynamically form a search result based on the text entered in the search string, without waiting for the “Find” button.

So, we have a table with UISearchBar to search. In this example, the DataSource will be a SQLite database (but it can also be an external data source with API access, for example). The database contains many records (several thousand), a search on it can take about 0.5 seconds.

In order to dynamically form a search output as the user enters a query, you need to implement the method - (void) searchBar: (UISearchBar *) searchBar textDidChange: (NSString *) searchText delegate UISearchBar:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { __weak ProductPickerTableViewController *weakSelf = self; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ const char *label = "ru.example.unique.search"; weakSelf.searchDispatchQueue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL); }); dispatch_async(self.searchDispatchQueue, ^{ NSArray *searchProducts = nil; if ([searchText length]) { searchProducts = [self.food productsBySearchPhrase:searchText]; } dispatch_async(dispatch_get_main_queue(), ^{ weakSelf.searchProducts = searchProducts; [weakSelf.tableView reloadData]; }); }); } 

')
We fill the DataSource of our controller not on the main thread, otherwise we will get a slowdown of the interface. After the data is received, update the table view (always on the main thread).

There is one drawback in this approach - each time the search string is changed, the database search method will be called up (or a network API request will be sent), which is completely optional when the user types the query fast enough or deletes it with the backspace key held down.



The first decision that occurred to me was to call the search methods (update DataSource) only if, for example, 0.1 seconds passes between character inputs. In the githaba, a canceled block implementation was found:

 // // dispatch_cancelable_block.h // sebastienthiebaud.us // // Created by Sebastien Thiebaud on 4/9/14. // Copyright (c) 2014 Sebastien Thiebaud. All rights reserved. // typedef void(^dispatch_cancelable_block_t)(BOOL cancel); NS_INLINE dispatch_cancelable_block_t dispatch_after_delay(NSTimeInterval delay, dispatch_block_t block) { if (block == nil) { return nil; } // First we have to create a new dispatch_cancelable_block_t and we also need to copy the block given (if you want more explanations about the __block storage type, read this: https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW6 __block dispatch_cancelable_block_t cancelableBlock = nil; __block dispatch_block_t originalBlock = [block copy]; // This block will be executed in NOW() + delay dispatch_cancelable_block_t delayBlock = ^(BOOL cancel){ if (cancel == NO && originalBlock) { originalBlock(); } // We don't want to hold any objects in the memory originalBlock = nil; }; cancelableBlock = [delayBlock copy]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ // We are now in the future (NOW() + delay). It means the block hasn't been canceled so we can execute it if (cancelableBlock) { cancelableBlock(NO); cancelableBlock = nil; } }); return cancelableBlock; } NS_INLINE void cancel_block(dispatch_cancelable_block_t block) { if (block == nil) { return; } block(YES); block = nil; } 


Using this implementation of the block to be canceled, you can rewrite the UISearchBar delegate method as follows:

 - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { __weak ProductPickerTableViewController *weakSelf = self; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ static const char *label = "ru.example.unique.search"; weakSelf.searchDispatchQueue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL); }); double searchDelay = 0.1; if (self.searchBlock != nil) { //We cancel the currently scheduled block cancel_block(self.searchBlock); } self.searchBlock = dispatch_after_delay(searchDelay, ^{ //We "enqueue" this block with a certain delay. It will be canceled if the user types faster than the delay, otherwise it will be executed after the specified delay dispatch_async(self.searchDispatchQueue, ^{ NSArray *searchProducts = nil; if ([searchText length]) { searchProducts = [self.food productsBySearchPhrase:searchText]; } dispatch_async(dispatch_get_main_queue(), ^{ weakSelf.searchProducts = searchProducts; [weakSelf.tableView reloadData]; }); }); }); } 


The variable searchDelay corresponds to the time interval between entering (or deleting) two characters in the search string. 0.1 sec will be enough to not cause multiple search methods when deleting the search string with the backspace key, 0.2 ... 0.3 sec is enough to quickly enter a query.

As a result, we get responsive in the user's opinion:

Source: https://habr.com/ru/post/306742/


All Articles