I am implementing mobile local search in iPhone application:
- (void)searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)searchText {
//Remove all objects first.
[copyListOfItems removeAllObjects];
if([searchText length] > 0) {
[ovController.view removeFromSuperview];
searching = YES;
letUserSelectRow = YES;
self.tableView.scrollEnabled = YES;
[self searchTableView];
}
else {
[self.tableView insertSubview:ovController.view aboveSubview:self.parentViewController.view];
searching = NO;
letUserSelectRow = NO;
self.tableView.scrollEnabled = NO;
}
[self.tableView reloadData];
}
- (void) searchTableView {
NSString *searchText = searchBar.text;
NSMutableArray *searchArray = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in listOfItems)
{
NSArray *array = [dictionary objectForKey:#"key_pdescription"];
[searchArray addObjectsFromArray:array];
}
//in requirement searchArray has more than 50k objects
for (NSString *sTemp in searchArray)
{
NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0)
[copyListOfItems addObject:sTemp];
}
[searchArray release];
searchArray = nil;
}
It's working fine for hundreds of records,but I have to implement for 50 thousands or more than that,that search is very very slow after typing 1 character I have to wait to enter next character.
I want to if someone has implemented some optimized algorithm for mobile local search,please guide me how to proceed.
you can use core data and limit the fetch result and populate the data on search result screen,and you can implementat pagination for the next set of records.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"predicateformart"];
[request setPredicate:predicate];
[request setFetchLimit:100];//100 records for example
The problem lies in the method searchTableView.
Firstly: do not create searchArray every time you search, better choice is build it on viewDidLoad.
Secondly: use
[NSArray filteredArrayUsingPredicate:(NSPredicate *)predicate]
instead of manual comparing.
Related
I would like to improve research within my UITableViewController.
Now i have an NSMutableArray like this:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:#"the dog is brown",#"the cat is red",#"the mouse is grey"];
Now if I write in my searchbar for example: "the cat", the table will show "the cat is red" and it is ok.
But if I write: "cat red", the table will be empty.
What can I do to improve this?
so it becomes like making the search now:
-(void)loadArray {
arraySearch = [[NSMutableArray alloc] initWithCapacity:[array count]];
[arraySearch addObjectsFromArray: array];
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
[arraySearch removeAllObjects];
for (int z=0; z<[array count]; z++) {
if ([[array objectAtIndex:z] rangeOfString:searchText options:NSCaseInsensitiveSearch].location !=NSNotFound) {
[arraySearch addObject:[array objectAtIndex:z]];
}
[self.tableView reloadData];
}
Thanks to all.
I FOUND the solution and I would like to share it with you:
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
[arraySearch removeAllObjects];
NSArray *listItems = [searchText componentsSeparatedByString:#" "];
NSMutableArray *arrayPredicate = [NSMutableArray array];
for (NSString *str in listItems) {
if ([str length] > 0) {
NSPredicate *pred = [NSPredicate predicateWithFormat:#"self contains [cd] %# OR self contains [cd] %#", str, str];
[arrayPredicate addObject:pred];
}
}
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:arrayPredicate];
[arraySearch addObjectsFromArray:[array filteredArrayUsingPredicate:predicate]];
[self.tableView reloadData];
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
[arraySearch removeAllObjects];
//Break the sentence into words
NSArray *listItems = [searchText componentsSeparatedByString:#" "];
//Iterate each word and create predicate statement
NSMutableArray *predicatesArray = [NSMutableArray array];
for (NSString *str in listItems) {
[predicatesArray addObject:[NSString stringWithFormat:#"SELF like[c] %#",str]];
}
//Concatenate predicate statements by conditional OR
NSPredicate* predicate = [NSPredicate predicateWithFormat:[predicatesArray componentsJoinedByString:#" OR "]];
//Filter the array against matching predicate
[arraySearch addObjectsFromArray:[array filteredArrayUsingPredicate:predicate]];
[self.tableView reloadData];
}
Using core data, data is being fetched properly and shown properly, issue is with the search that it does not filter the results, whatever I type in the search bar, it does show the same table view with same data in the filtered results..
- (void)viewDidLoad
{
[super viewDidLoad];
if (context == nil)
{
context = [(VektorAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
app = [[UIApplication sharedApplication] delegate];
[self getData];
// create a filtered list that will contain products for the search results table.
filteredListItems = [[NSMutableArray alloc] initWithCapacity:[self.plateNumbers count]];
// restore search settings if they were saved in didReceiveMemoryWarning.
if (self.savedSearchTerm){
[self.searchDisplayController setActive:self.searchWasActive];
[self.searchDisplayController.searchBar setSelectedScopeButtonIndex:self.savedScopeButtonIndex];
[self.searchDisplayController.searchBar setText:savedSearchTerm];
self.savedSearchTerm = nil;
}
}
Fetching data from core data:
-(void)getData {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Favouritesdata" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setFetchBatchSize:20];
[request setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"licenseplate" ascending:NO];
NSArray *newArray = [[NSArray alloc]initWithObjects:sort, nil];
[request setSortDescriptors:newArray];
NSLog(#"newArray: %#", newArray);
NSError *error;
results = [[context executeFetchRequest:request error:&error] mutableCopy];
plateNumbers = [results valueForKey:#"licenseplate"];
NSLog(#"plateNumbers: %#", plateNumbers);
[self setLicensePlateArray:results];
[self.favouritesTable reloadData];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == favouritesTable) {
return [licensePlateArray count];
} else { // handle search results table view
return [filteredListItems count];
}
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == favouritesTable) {
cellValue = [licensePlateArray objectAtIndex:indexPath.row];
} else { // handle search results table view
cellValue = [filteredListItems objectAtIndex:indexPath.row];
}
static NSString *CellIdentifier = #"vlCell";
VehicleListCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSLog(#"Cell Created");
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"VehicleListCell" owner:nil options:nil];
for (id currentObject in nibObjects) {
if ([currentObject isKindOfClass:[VehicleListCell class]]) {
cell = (VehicleListCell *)currentObject;
}
}
NSInteger cellVal = indexPath.row;
NSLog(#"indexPath.row: %i", cellVal);
UILongPressGestureRecognizer *pressRecongnizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(tableCellPressed:)];
pressRecongnizer.delegate = self;
pressRecongnizer.minimumPressDuration = 0.5f;
[cell addGestureRecognizer:pressRecongnizer];
[pressRecongnizer release];
}
cell.textLabel.font = [UIFont systemFontOfSize:10];
Favouritesdata *favdata = [results objectAtIndex:indexPath.row];
cell.licPlate.text = [favdata licenseplate];
NSLog(#"cellvalue for cellforRow: %#", cell.licPlate.text);
return cell;
}
Search bar implementation:
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
NSPredicate *resultPredicate = [NSPredicate
predicateWithFormat:#"SELF contains[cd] %#",
searchText];
self.filteredListItems = [self.plateNumbers filteredArrayUsingPredicate:resultPredicate];
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
/* [self filterContentForSearchText:searchString scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
*/
if ([[searchString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length])
self.favouritesTable = controller.searchResultsTableView;
return YES;
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
[self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:searchOption]];
return YES;
}
How to resolve this issue ?
Just make code clear before and define every methods, you are just setting predicate, not firing query for it.
just make separate method and just pass search text as parameter to that method for predicate and fire fetch query then reload table.
I am trying to add a UISearchBar to my UITableView.
This is the method they use to search:
- (void) searchTableView {
NSString *searchText = searchBar.text;
NSMutableArray *searchArray = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in listOfItems)
{
NSArray *array = [dictionary objectForKey:#"Countries"];
[searchArray addObjectsFromArray:array];
}
for (NSString *sTemp in searchArray)
{
NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0)
[copyListOfItems addObject:sTemp];
}
[searchArray release];
searchArray = nil;
}
But I don't have a dictionary, I just want to search in my array 'exerciseArray' which is init like this:
NSString *path = [[NSBundle mainBundle]pathForResource:#"data" ofType:#"plist"];
NSMutableArray *rootLevel = [[NSMutableArray alloc]initWithContentsOfFile:path];
self.exerciseArray = rootLevel;
[rootLevel release];
When I NSLog it, I get:
exerciseArray: (
{
exerciseName = "Balance Board";
},
{
exerciseName = "Barbell Seated Calf Raise";
},
{
exerciseName = "Calf Press On The Leg Press Machine";
},
{
exerciseName = "Calf Raises- With Bands";
},
So can anyone help me modify the search method to fit the array I have? It seems to have a key in it.
I tried this but it did not work and crashed at line NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];
with error *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary rangeOfString:options:]: unrecognized selector sent to instance 0x2d6450
- (void) searchTableView {
NSString *searchText = searchBar.text;
NSLog(#"exerciseArray: %#", exerciseArray);
for (NSString *sTemp in listOfItems)
{
if (sTemp) {
NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0)
[copyListOfItems addObject:sTemp];
}
}
}
Looks like you have an array of dictionaries with one key/value pair in each dictionary (exerciseName). so for your dataset, it should be this:
for (NSDictionary *dictionary in exerciseArray)
{
NSArray *array = [dictionary objectForKey:#"exerciseName"];
[searchArray addObjectsFromArray:array];
}
...
If you post your VC code related to the array and also your tableview datasource methods, I have can modify it and show you what you need to do.
For searching from table view i have tried this and i got success hope it will help you......
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
self.searching=NO;
NSString *originalString;
[self.filteredProductArray removeAllObjects];
for (int i=0; i<self.exerciseArray.count; i++)
{
originalString=[[[self.exerciseArray objectAtIndex:i] objectForKey:#"exerciseName"] lowercaseString];
if (!([originalString rangeOfString:[searchText lowercaseString]].location==NSNotFound))
{
[self.filteredProductArray addObject:[self.exerciseArray objectAtIndex:i]];
}
}
if ([searchText isEqual:#""])
{
self.searching=NO;
}
[self.tableView reloadData];
}
I believe NSPredicates are good way to search in arrays.. You can refer to this link.. Using NSPredicate to filter an NSArray based on NSDictionary keys
Can someone exlain this code in detail and suggest how I can sort on the name?
- (void)handleSearchForTerm:(NSString *)searchTerm {
selectButton.enabled = NO;
NSMutableArray *sectionsToRemove = [[NSMutableArray alloc] init];
[self resetSearch];
for (NSString *key in self.keys) {
NSMutableArray *array = [Categories valueForKey:key];
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for (NSString *name in array) {
if ([name rangeOfString:searchTerm
options:NSCaseInsensitiveSearch].location == NSNotFound)
[toRemove addObject:name];
}
if ([array count] == [toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
[toRemove release];
}
[self.keys removeObjectsInArray:sectionsToRemove];
[sectionsToRemove release];
[table reloadData];
}
- (void)handleSearchForTerm:(NSString *)searchTerm {
selectButton.enabled = NO;
NSMutableArray *sectionsToRemove = [[NSMutableArray alloc] init]; //creating an mutable array, which can be altered in progress.
[self resetSearch]; //calling some other method not displayed in the code here
for (NSString *key in self.keys) { //for each key,
NSMutableArray *array = [Categories valueForKey:key]; //you get the key's category
NSMutableArray *toRemove = [[NSMutableArray alloc] init]; //and initialize the array for items you wish to remove
for (NSString *name in array) { //then, for each name
if ([name rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location == NSNotFound)
[toRemove addObject:name];
//you check if the name is in range of the searchterm, with which you call this function
//if you don't find it, you add it to the removal list
}
if ([array count] == [toRemove count])
[sectionsToRemove addObject:key]; //if you haven't found any name, it means you've added all the names in the toRemove array
[array removeObjectsInArray:toRemove]; //that means the count of both arrays are the same
[toRemove release]; //so you remove that section entirely, since there is no result there
}
[self.keys removeObjectsInArray:sectionsToRemove]; //you remove all the keys which aren't found
[sectionsToRemove release]; //leaving you the keys which are found
[table reloadData]; //you reload the table with the found results only
}
I hope it all made sense, I did my best commenting it ;)
Good luck.
This code is the search code
- (void) searchTableView {
NSLog(#"4");
NSString *searchText = searchBar.text;
NSMutableArray *searchArray = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in listOfItems)
{
NSArray *array = [dictionary objectForKey:#"Countries"];
[searchArray addObjectsFromArray:array];
}
for (NSString *sTemp in searchArray)
{
NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0)
[copyListOfItems addObject:sTemp];
}
//NSLog(#"Count - %d",[copyListOfItems count]);
[searchArray release];
searchArray = nil;
}
I want to implement this type of search in my array but i am getting error in below line.
[searchArray addObjectsFromArray:array];
when control comes on this line application shutdown every time can any one help me ?
Thanks
That line will give you an error only if array has nothing in it(nil value).
So check what is the value of array using nslog.