Delay UISearchbar parsing - iphone

I have a UISearchbar in my app. This is a dynamic search and as the user enters text, a remote database is searched via a remote API call (I think it is through REST).
The table view gets refreshed dynamically, as the user types. I am using NSXMLParser for parsing the XML results. (so 3 delegate methods; didStartElement, didEndElement)
In some cases, there are duplicate entries shown in the results
e.g. If user has typed YAH, it shows YAHOO 3-4 times. I'm not sure why.
How can I reduce the number of times the parsing is done, or how to delay the parsing, so that it does not make a request for every character entered/deleted by the user.
This, I am assuming, might fix the problem.

One thing you can do is introduce a delay before you send off the remote API call, instead of sending one query for every character.
// Whenever UISearchbar text changes, schedule a lookup
- (void)searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)text {
// cancel any scheduled lookup
[NSObject cancelPreviousPerformRequestsWithTarget:self];
// start a new one in 0.3 seconds
[self performSelector:#selector(doRemoteQuery) withObject:nil afterDelay:0.3];
}

Here are the relevant parts of a method I use in one of my apps to remove duplicates from a web service result.
NSMutableArray *mutableResults = [[myResults mutableCopy] autorelease];
NSMutableSet *duplicates = [NSMutableSet set];
NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet];
for (NSString *result in mutableResults)
{
if (![duplicates containsObject:result])
[duplicates addObject:result];
else
[indexesToRemove addIndex:[mutableResults indexOfObject:object]];
}
[mutableResults removeObjectsAtIndexes:duplicates];
return mutableResults;

Related

Speed up search using dispatch_async?

I'm trying to speed up my app search , it get lags when there is a lot of data.
so i'm trying to split search Predicate on UI by using dispatch_async not dispatch_sync cause no different if I use it.
The problem is when i use dispatch_async, the app crash sometimes because [__NSArrayI objectAtIndex:]: index "17" beyond bounds.
I now this happened because lets say the first one still work and reload the tableView and continue search will change the array size depend on result so in this case "CRASH" :(
this is my code:
dispatch_async(myQueue, ^{
searchArray = [PublicMeathods searchInArray:searchText array:allData];
} );
if(currentViewStyle==listViewStyle){
[mytable reloadData];
}
and i've tried this :
dispatch_async(myQueue, ^{
NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
dispatch_sync(dispatch_get_main_queue(), ^{
searchArray = tmpArray;
[mytable reloadData];
});
});
but in this case the lags still there.
Update -1- :
The search Predicate takes just 2ms :) after hard work :)
but the keyboard still lags when the user searches, so the only thing I do after get result is reload table "change in UI" this what I think make it lags,
So what I search for split this two operation "typing on keyboard & refresh UI".
Update -2- :
#matehat https://stackoverflow.com/a/16879900/1658442
and
#TomSwift https://stackoverflow.com/a/16866049/1658442
answers work like a charm :)
If searchArray is the array that is used as table view data source then this array must
only be accessed and modified on the main thread.
Therefore, on the background thread, you should filter into a separate temporary array first. Then you assign the temporary array to searchArray on the main thread:
dispatch_async(myQueue, ^{
NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
dispatch_sync(dispatch_get_main_queue(), ^{
searchArray = tmpArray;
[mytable reloadData];
});
});
Update: Using a temporary array should solve the crash problem, and using a background thread helps to keep the UI responsive during the search. But as it turned out in the discussion, a major reason for the slow search might be the complicated search logic.
It might help to store additional "normalized" data (e.g. all converted to lower-case, phone numbers converted to a standard form, etc ...) so that the actual search can be done with
faster case-insensitive comparisons.
One solution might be to voluntarily induce a delay between searches to let the user type and let the search be performed asynchronously. Here's how:
First make sure your queue is created like this :
dispatch_queue_t myQueue = dispatch_queue_create("com.queue.my", DISPATCH_QUEUE_CONCURRENT);
Have this ivar defined in your class (and set it to FALSE upon initialization):
BOOL _scheduledSearch;
Write down this macro at the top of your file (or anywhere really, just make sure its visible)
#define SEARCH_DELAY_IN_MS 100
And instead of your second snippet, call this method:
[self scheduleSearch];
Whose implementation is:
- (void) scheduleSearch {
if (_scheduledSearch) return;
_scheduledSearch = YES;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)((double)SEARCH_DELAY_IN_MS * NSEC_PER_MSEC));
dispatch_after(popTime, myQueue, ^(void){
_scheduledSearch = NO;
NSString *searchText = [self textToSearchFor];
NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
dispatch_async(dispatch_get_main_queue(), ^{
searchArray = tmpArray;
[mytable reloadData];
});
if (![[self textToSearchFor] isEqualToString:searchText])
[self scheduleSearch];
});
}
[self textToSearchFor] is where you should get the actual search text from.
Here's what it does :
The first time a request comes in, it sets the _scheduledSearch ivar to TRUE and tells GCD to schedule a search in 100 ms
Meanwhile any new search requests is not taken care of, because a search is going to happen anyway in a few ms
When the scheduled search happens, the _scheduledSearch ivar is reset to FALSE, so the next request is handled.
You can play with different values for SEARCH_DELAY_IN_MS to make it suit your needs. This solution should completely decouple keyboard events with workload generated from the search.
First, a couple notes on the code you presented:
1) It looks as if you're likely queuing up multiple searches as the user types, and these all have to run to completion before the relevant one (the most recent one) updates the display with the desired result set.
2) The second snippet you show is the correct pattern in terms of thread safety. The first snippet updates the UI before the search completes. Likely your crash happens with the first snippet because the background thread is updating the searchArray when the main thread is reading from it, meaning that your datasource (backed by searchArray) is in an inconsistent state.
You don't say if you're using a UISearchDisplayController or not, and it really doesn't matter. But if you are, one common issue is not implementing - (BOOL) searchDisplayController: (UISearchDisplayController *) controller shouldReloadTableForSearchString: (NSString *) filter and returning NO. By implementing this method and returning NO you are turning off the default behavior of reloading the tableView with each change to the search term. Instead you have opportunity to kick off your asynchronous search for the new term, and update the UI ([tableview reloadData]) only once you have new results.
Regardless of whether you're using UISearchDisplayController you need to take a few things into consideration when implementing your asynchronous search:
1) Ideally you can interrupt a search-in-progress and cancel it if the search is no longer useful (e.g. the search term changed). Your 'searchInArray' method doesn't appear to support this. But it's easy to do if your just scanning an array.
1a) If you can't cancel your search, you still need a way at the end of the search to see if your results are relevant or not. If not, then don't update the UI.
2) The search should run on a background thread as to not bog down the main thread and UI.
3) Once the search completes it needs to update the UI (and the UI's datasource) on the main thread.
I put together sample project (here, on Github) that performs a pretty inefficient search against a large list of words. The UI remains responsive as the user types in their term, and the spawned searches cancel themselves as they become irrelevant. The meat of the sample is this code:
- (BOOL) searchDisplayController: (UISearchDisplayController *) controller
shouldReloadTableForSearchString: (NSString *) filter
{
// we'll key off the _currentFilter to know if the search should proceed
#synchronized (self)
{
_currentFilter = [filter copy];
}
dispatch_async( _workQueue, ^{
NSDate* start = [NSDate date];
// quit before we even begin?
if ( ![self isCurrentFilter: filter] )
return;
// we're going to search, so show the indicator (may already be showing)
[_activityIndicatorView performSelectorOnMainThread: #selector( startAnimating )
withObject: nil
waitUntilDone: NO];
NSMutableArray* filteredWords = [NSMutableArray arrayWithCapacity: _allWords.count];
// only using a NSPredicate here because of the SO question...
NSPredicate* p = [NSPredicate predicateWithFormat: #"SELF CONTAINS[cd] %#", filter];
// this is a slow search... scan every word using the predicate!
[_allWords enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
// check if we need to bail every so often:
if ( idx % 100 == 0 )
{
*stop = ![self isCurrentFilter: filter];
if (*stop)
{
NSTimeInterval ti = [start timeIntervalSinceNow];
NSLog( #"interrupted search after %.4lf seconds", -ti);
return;
}
}
// check for a match
if ( [p evaluateWithObject: obj] )
{
[filteredWords addObject: obj];
}
}];
// all done - if we're still current then update the UI
if ( [self isCurrentFilter: filter] )
{
NSTimeInterval ti = [start timeIntervalSinceNow];
NSLog( #"completed search in %.4lf seconds.", -ti);
dispatch_sync( dispatch_get_main_queue(), ^{
_filteredWords = filteredWords;
[controller.searchResultsTableView reloadData];
[_activityIndicatorView stopAnimating];
});
}
});
return FALSE;
}
- (BOOL) isCurrentFilter: (NSString*) filter
{
#synchronized (self)
{
// are we current at this point?
BOOL current = [_currentFilter isEqualToString: filter];
return current;
}
}
i believe your crash is indeed solved by the embedding of the display of the UI element for which searchArray is the backing element in a call to GrandCentralDispatch inside of the other call (as you show in your updated original post). that is the only way to make sure you are not causing the elements of the array to change behind the scenes while the display of the items associated with it is taking place.
however, i believe if you are seeing lag, it is not so much caused by the processing of the array at 2ms or the reload that takes 30ms, but rather by the time it takes GCD to get to the internal dispatch_sync call on the main queue.
if, by this point, you have managed to get the processing of your array down to only 2ms in the worst case (or even if you've managed to get it down to less than 30ms, which is about the time it takes to process a frame in the main run loop at 30 fps), then you should consider abandoning GCD altogether in your effort to process this array. taking 2ms on the main queue to process your array is not going to cause any buggy behavior.
you may have lag elsewhere (i.e. if you are incrementing search results by trying to go out to the net to get the results, you may want to do the call and then process the response on your separate dispatch queue), but for the times you are talking about, this bit of processing doesn't need to be split out onto separate queues. for any hard-core processing that takes over 30ms, you should consider GCD.
I suspect your problem is that allData is shared between the main queue and the background queue. If you make a change in allData on the main queue, that may shorten allData in the background queue, causing an index that used to be valid to become invalid.
It's also possible that the problem is not allData itself, but some array within the objects in allData. Try setting a breakpoint on exceptions (in Xcode, open the Breakpoints source list, click the plus button at the bottom, and choose "Add Exception Breakpoint...") so you can see exactly where the error occurs.
In either case, you have two possible solutions:
Copy the offending object before using it in the search. This protects the background queue from changes in the main queue, but depending on what you need to copy, it may be difficult to get the changes back into the UI—you might have to match the copies back to their originals.
Use a lock (like #synchronized) or a per-object queue to ensure only one queue is using the object at a time. NSManagedObjectContext uses the latter approach for its -performBlock: and -performBlockAndWait: methods. It may be a little tricky to do this without blocking the main queue, though.
Try to modify your functions the next way:
function prototype;
- (void)searchInArray:searchText array:allData complete: (void (^)(NSArray *arr)) complete;
function itself
- (void)searchInArray:searchText array:allData complete: (void (^)(NSArray *arr)) complete {
NSArray * array = [NSArray new];
// function code
complete(array)//alarming that we have done our stuff
}
and when you are calling this function
dispatch_queue_t searchQueue = dispatch_queue_create("com.search",NULL);
dispatch_async(searchQueue,^{
[PublicMeathods searchInArray:searchText array:allData complete:^(NSArray *arr) {
searchArray = arr;
dispatch_async(dispatch_get_main_queue(), ^{
[myTable reloadData];
});
}];
});
Hope it will help you)
I found a simple solution with the same spirit of the solution presented by Matehad (wait some time and perform a search only if the user doesn't input anything else). Here it is:
Declare 2 global counters and a global string:
int keyboardInterruptionCounter1 = 0, int keyboardInterruptionCounter2 = 0 and NSString *searchTextGlobal
On the searchBar function do this:
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
keyboardInterruptionCounter1++;
searchTextGlobal = searchText;//from local variable to global variable
NSTimeInterval waitingTimeInSec = 1;//waiting time according to typing speed.
//waits for the waiting time
[NSTimer scheduledTimerWithTimeInterval:waitingTimeInSec target:self selector:#selector(timerSearchBar:) userInfo:nil repeats:NO];
}
-(void)timerSearchBar:(NSTimer *)timer{
keyboardInterruptionCounter2++;
// enters only if nothing else has been typed.
if (keyboardInterruptionCounter2 == keyboardInterruptionCounter1) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
//do the search with searchTextGlobal string
dispatch_async(dispatch_get_main_queue(), ^{
//update UI
});
});
}
}
Explanation: The search is performed only if both counters are the same, this only happens if the user has typed and waited .52 sec without typing anything else. Instead, if the users types fast enough, then no query is done. The solution can be done with or without threading.
Martin R has posted a correct answer. The only thing to point out that instead of
dispatch_sync(dispatch_get_main_queue()
it should be
dispatch_async(dispatch_get_main_queue()
The complete code in Swift would be:
let remindersFetcherQueue = dispatch_queue_create("com.gmail.hillprincesoftware.remindersplus", DISPATCH_QUEUE_CONCURRENT)
dispatch_sync(remindersFetcherQueue) {
println("Start background queue")
estore.fetchRemindersMatchingPredicate(remindersPredicate) {
reminders in
// var list = ... Do something here with the fetched reminders.
dispatch_async(dispatch_get_main_queue()) {
self.list = list // Assign to a class property
self.sendChangedNotification() // This should send a notification which calls a function to ultimately call setupUI() in your view controller to do all the UI displaying and tableView.reloadData().
}
}
}

How to get NSString variable value from NSObject to ViewController

I am trying to set up an object to control all of my data so it can set things up in the background to it appears my tableviews load faster than they do now etc.
This is what I am trying to achieve.
I am setting a variable in the NSObject from the secondVC when the tableviewcell is selected like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Access selected cells content (cell.textLabel.text)
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//Parent view logic (sends info back to the correct cell in parent view)
if (parentViewSelectedIndexPath.section == 0)
{
if (parentViewSelectedIndexPath.row == 0)
{
//Predicates restrict the values that will be returned from the query
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K like %#",#"MANUFACTURER",cell.textLabel.text];
NSArray *filterArray = [myDataArray filteredArrayUsingPredicate:predicate];
//[[self delegate] setManufactureSearchFields:filterArray withIndexPath:indexPath]; //This is where I pass the value back to the mainview
//Using Object
VehicleControllerNSObject *vehicleControllerNSObject = [[VehicleControllerNSObject alloc] init];
[vehicleControllerNSObject setFirstCell:filterArray];
}
//etc
At the end there you can see the method that is getting set up in the VechicleControllerNSObject which looks like this.
-(void)setFirstCell:(NSArray *)array{
manufactureSearchObjectStringFVC = [[array valueForKey:#"MANUFACTURER"] objectAtIndex:0];
NSLog(#"%#", manufactureSearchObjectStringFVC); // this prints the correct value to the console
}
As you can see this prints the correct output fine.
however I have no idea how to call manufactureSearchObjectStringFVC and pass the value it holds into the uitableviewcell that I would like to pass it in on my firstviewcontroller.
This is what I have for testing atm.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
VehicleControllerNSObject *vehicleControllerNSObject = [[VehicleControllerNSObject alloc] init];
manufactureSearchObjectString = vehicleControllerNSObject.manufactureSearchObjectStringFVC;
NSLog(#"%#", vehicleControllerNSObject.manufactureSearchObjectStringFVC);
}
That nslog prints null..
I have three questions
1, how do I get the correct value into the first valuecontroller.
2, should I be using viewDidAppear like this?.. I think not.. how can I do this better
3, Do you think this is a good way of doing this type of thing, as in the future i would like to use the NSObjectClass to parse info, cache etc all behind the senses leaving the views to just display when the data is ready hopefully helping performance..
Any help would be hugely appreciated as I really want to learn this stuff as i know its important for me to know.
Your question is so beautifully and clearly formatted and diagrammed that it seems a shame to ask you to do a search. But here it is:
Search for Sharing Data between View Controllers
You'll find many good discussions about sharing data between view controllers.
Briefly, though, I can tell you why your code isn't working. In your tableView:didSelectRowAtIndexPath: method, you are creating (alloc/init) a new instance of your VehicleControllerNSObject class each time. Then back in your first view controller on viewDidAppear:, again you are creating (alloc/init) a whole new instance each time.
So you have multiple objects coming and going and they have nothing to do with each other. It's a bit like giving some important information to one person at a bus station and then later randomly picking some other person out and trying to retrieve that same information from her.
So one quick idea would be to create just once instance of your VehicleControllerNSObject (just an aside, that's a bit of a strange name for a class since generally all objective-c objects are descendants of NSObject anyway. I'm just going to call that VehicleController for now)
So let's say you wanted a 'sharedInstance' of VehicleController. You could add a class method to VehicleController to give you a way to easily get that one sharedInstance:
+(VehicleController*)sharedInstance {
static VehicleController *sharedInstance_ = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance_ = [[VehicleController alloc] init];
});
return sharedInstance_;
}
So to get that instance in methods in other classes you can just do something like :
VehicleController *sharedController = [VehicleController sharedInstance];
sharedController.someProperty = someValue;
// and then back in your first view controller, similarly:
VehicleController *sharedController = [VehicleController sharedInstance];
id someValue = sharedController.someProperty;
Again, check the search, many people have had good discussions on this. This is just one approach. I hope it at least makes sense why your code wasn't working.
Hope that helps.
To answer question 3. No.
I think that the best way to do something like this would be to use Core Data and it's NSManagedObject.
A combination of UITableViewController and NSFetchedResultsController that is feed from a Core Data sqlite backing store, if well set would feed and keep your UITableView updated.
It would be to long to describe all in here. So I will stop there.
If you don't want to go with that there is always the possibility to use a shared pointers to a mutable object or to use a singleton object to communicate information between UIViewController.

I'm having issues inputing a name if applicable

I have a function here that upon completing a single round, if your score is higher than either a default score entry or a newly placed high score then it will swap its data with your data and push everything else down. removing the last entry from the list. currently this is just one exchange and for functions sake I'm going to hard code it and then refactor it later.
My main problem is that when I set up a text input view to capture the players name execution continues immediately without the players input and crashes the game. I commented out the line that sets the text because I have a default value in place just in case any attempt that I try to make fails. How can I get Execution to wait for a moment while input is taken? Would I have to set up a delegate method? If so I'm still a bit confused by delegates. I could set it up to work but I don't understand it, so I wouldn't be able to do any other special custom tasks with it. I've worked on it for a while and got no further...
-(void)saveData:(ScoreKeep *)stats{
NSMutableDictionary *swap = [[NSMutableDictionary alloc]init];//used for swaping entries
NSString *filePath = [self pathOfFile];
NSLog(#"Writing to %#", filePath);
if ([[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
NSLog(#"Loading previous dictionary to save...");
dataDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
if ([dataDictionary objectForKey:#"1"]) {
NSMutableDictionary *highScore = [dataDictionary objectForKey:#"1"];
if ([stats.score intValue] > [[highScore objectForKey:#"SCORE"] intValue]) {
NSLog(#"You Win! score: %# highscore: %#", stats.score,[NSNumber numberWithInt:[[highScore objectForKey:#"SCORE"] intValue]] );
stats = [[ScoreKeep alloc] initWithNibName:#"Scorekeep" bundle:nil];
NSLog(#"Setting up name entry");
[self.view addSubview:stats.view]; //New view is added so that the player can input data(Assume it is complete);
//stats.nameTag = setName.nameTag;//This line is executed before the new view is dismissed causing an error to occur
[stats setupDictionary]; // It just goes down hill from here if the previous line is uncommented
[dataDictionary setObject:stats.sComponents forKey:#"1"];
}else {
NSLog(#"You Lose: %# highscore: %#", stats.score,[NSNumber numberWithInt:[[highScore objectForKey:#"SCORE"] intValue]] );
}
NSLog(#"Got first place entry");
}else {
NSLog(#"Initilizing Score");
}
}else{
NSLog(#"Creating new dictionary to save...");
dataDictionary = [[NSMutableDictionary alloc]init];
}
[dataDictionary writeToFile:filePath atomically:YES];
}
Help would greatly be appreciated. If more information is needed I'd be happy to provide.
by the way ScoreKeep is an object that contains a dictionary and a function to create a dictionary such that it can set any values I need and package them into sComponents(the dictionary to be entered into the main savefile)
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#class omphalosUtility;
#pragma mark -
#pragma mark Saving data
#pragma mark -
static inline void poop(){
NSLog(#"POOP");
}
I'm going to try making a utility file that works independently of the app so that I Can update files and perform other universal operations such as saving when needed. Its a step in a direction that i'd like to take.
If i get it right, (The code is really nasty, man...) your problem is that you are trying to present a View Controller with the wrong way.
Correct me if i'm wrong, is ScoreKeep is a ViewController? if so, you have to name it properly. that's for a start.
Second, you cant present another view controller only by adding its "view" property to the current view controller's View Hierarchy. that way the view will not respond properly to the events.
the correct way to to what you'r trying to do is by presenting the ScoreKeep ViewController modally.
there is no other right way to do this without using delegation. you will have to acquire this technique.
Your view controller that responsible for getting the name from the user need to have a way to tell it's master view controller that the user entered a name. and that is achieved through delegation.
What you should do:
Basically you create a protocol called something like "NamePrompterViewControllerDelegate"
that will have at least one method that will be called when the user will done entering his name.
Your ScoreKeepViewController should have an instance variable that implemented the protocol (Look at the apple documentation on protocols for assistance)
Your main view controller (the one that contains the method you added) then should implement the protocol you created, and set itself as the delegate of ScoreKeep like that:
stats = [[ScoreKeep alloc] initWithNibName:#"Scorekeep" bundle:nil];
stats.delegate = self;
For more info on presenting and dismissing ViewControllers modally you should read the documentation at Apple Documentation
I hope i helped you, there is just a lot to cover and it hardly can be done by writing an answer.
Feel free to ask more for clearance.

Delegate confusion .. How do I find out when several delegates have finished their task?

I've built a basic Web XML to Core Data parsing system, but I am confused about how to set off several parsers at once, and know when they are all done.
This is my current setup, which gets just one parsed xml file ("news"). But I have several xml files I need to parse ("sport", "shop" etc). How would set all of these off, and know when they are all done?
// ViewController.m
DataGrabber *dataGrabber = [[DataGrabber alloc] init];
dataGrabber.delegate = self;
[dataGrabber getData:#"news"];
// DataGrabber delegate method (within ViewController) which gets called when dataGrabber has got all of the XML file
- (void) dataGrabberFinished:(DataGrabber *)dataGrabber
{
NSManagedObjectContext *context = [self managedObjectContext];
NSError *parseError = nil;
// Parser puts the xml into core data. Do I need delegate on this too?
Parser *xmlParse = [[Parser alloc] initWithContext:context];
[xmlParse parseXMLFileWithData:dataGrabber.payload parseError:&parseError];
[xmlParse release];
}
(this a follow-on from this question - Returning data from data-grabbing class from web? )
One option is to count how many you create and then have each one call back to a method that counts down from that total. When its back to zero they are all done.
You will need to set up a delegate for the parser just like you did for the downloader.

Adjusting iPhone TableSearch Algorithm to Prevent UI Delay

Much of my code is based off of Apple's TableSearch example, however my app contains 35,000 cells that need to be searched rather than the few in the example. There isn't much documentation online about UISearchDisplayController since it is relatively new. The code I am using is as follows:
- (void)filterContentForSearchText:(NSString*)searchText {
/*
Update the filtered array based on the search text and scope.
*/
[self.filteredListContent removeAllObjects]; // First clear the filtered array.
/*
Search the main list for products whose type matches the scope (if selected) and whose name matches searchText; add items that match to the filtered array.
*/
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
for (Entry *entry in appDelegate.entries)
{
if (appDelegate.searchEnglish == NO) {
NSComparisonResult result = [entry.gurmukhiEntry compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame)
{
[self.filteredListContent addObject:entry];
}
}
else {
NSRange range = [entry.englishEntry rangeOfString:searchText options:NSCaseInsensitiveSearch];
if(range.location != NSNotFound)
{
[self.filteredListContent addObject:entry];
}
}
}}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self filterContentForSearchText:searchString];
[self.view bringSubviewToFront:keyboardView];
// Return YES to cause the search result table view to be reloaded.
return YES;}
My problem is that there is a bit of a delay after each button is pressed on the keyboard. This becomes a usability issue because the user has to wait after typing in each character as the App searches through the array for matching results. How can this code be adjusted so that the user can continually type without any delays. In this case, it is ok for a delay in the time it takes for the data to reload, but it should not hold up the keyboard in typing.
Update:
One way to acccomplish searchching while typing without "locking up" the UI, is to use threads.
So you can call the the method that performs the sorting with this method:
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
Which will keep it out of the main thread, allowing the UI update.
You will have to create and drain your own Autorealease pool on the background thread.
However, when you want to update the table you will have to message back to the main thread (all UI updates MUST be on the main thread):
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
You could also get a little more control by using NSOperation/NSOperationQueue Or NSThread.
Note that implementing threads is fraught with peril. You will have to make sure your code is thread-safe and you may get unpredictable results.
Also, here are other stackoverflow answers that may help:
Using NSThreads in Cocoa?
Where can I find a good tutorial on iPhone/Objective-C multithreading?
Original answer:
Don't perform the search until the user presses the "search" button.
There is a delegate method you can implement to catch the pressing of the search button:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar;