I have a UISearchResultsController which is filtering my NSFetchedResultsController and putting the filtered data into an array. Currently, I use an NSPredicate to take the search bar content and apply the filter. Here's my predicate:
[filteredArray removeAllObjets];
for(Account *account in unfilteredResults){
NSPredicate *predicate;
if(controller.searchBar.selectedScopeButtonIndex == 0){
predicate = [NSPredicate predicateWithFormat:#"accountFirstName BEGINSWITH[cd] %#", searchString];
}else if(controller.searchBar.selectedScopeButtonIndex == 1){
predicate = [NSPredicate predicateWithFormat:#"accountLastName BEGINSWITH[cd] %#", searchString];
}else if(controller.searchBar.selectedScopeButtonIndex == 2){
predicate = [NSPredicate predicateWithFormat:#"group.groupName CONTAINS[cd] %#", searchString];
}
if([predicate evaluateWithObject:account]){
[self.filteredArray addObject:account];
}
}
I'm trying to filter the accounts based on either first name, last name, or group name. I know that these operations are slow, but what can be done to make them faster?
Edit:
I just noticed that I'm recreating the predicate every iteration, but I still think that there should be a better way. I did see something about doing a binary compare in an Apple video, but I have no idea how to convert a search string into a binary string and less how to get the "next greatest" value.
How do I replace the BEGINSWITH statement with something more efficient?
I'm assuming your data objects are CoreData objects, if so have you marked the properties you are searching on as indexed?
It is too old post, but can help to somebody else. With this code you will create only one instance of NSPredicate.
NSPredicate *predicate;
if(controller,searchBar,.selectedScopeButtonIndex == 0){
predicate = [NSPredicate predicateWithFormat:#"accountFirstName BEGINSWITH[cd] %#", searchString];
}else if(controller,searchBar,.selectedScopeButtonIndex == 1){
predicate = [NSPredicate predicateWithFormat:#"accountLastName BEGINSWITH[cd] %#", searchString];
}else if(controller,searchBar,.selectedScopeButtonIndex == 2){
predicate = [NSPredicate predicateWithFormat:#"group.groupName CONTAINS[cd] %#", searchString];
}
self.filteredArray = [unfilteredResults filteredArrayUsingPredicate:predicate];
Related
I'm using to get some of the content of nsmutable array and it work fine if I don't use nsstring to make the query:
NSLog(#"user information %#", [usersInfo filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K == 'Joe", #"id"]]);
But try to use a nsstring to query for the user it doesn't work:
NSString *user="Joe";
NSLog(#"user information %#", [usersInfo filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K == user", #"id"]]]);
any of you knows what I'm doing wrong? or what would be the best of doing it using NSString to query for users?
When you write
NSString *user = #"Joe";
... [NSPredicate predicateWithFormat:#"%K == user", #"id"]
you seem to expect that "user" in the predicate is replaced by the contents ("Joe") of the NSString variable, but this is not correct.
You have to give the string
as another argument to the predicate and add the %# format that will be expanded by the string.
NSString *user = #"Joe";
... [NSPredicate predicateWithFormat:#"%K == %#", #"id", user]
Here %K (which is var arg substitution for a key path) will be
substituted by the key "id", and %# (which is
var arg substitution for an object value) will be substituted
by the contents of the user variable.
Using %K expansion instead of
[NSPredicate predicateWithFormat:#"id == %#", user]
has the advantage that it works correctly even if the key is a
reserved word in the predicate format string syntax.
I am unable to gues why you are using "%K == 'Joe"
Anyways, You can use predicate as:
[NSPredicate predicateWithFormat:#"object.property LIKE[c] %#", stringValue];
Or,
[NSPredicate predicateWithFormat:#"object.property==[c] %#", stringValue];
try this:
NSString *user="Joe";
NSLog(#"user information %#", [usersInfo filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF contains [cd] %#", user]]]);
Make your predicate format string before don't try to add property name inside predicate format :
NSString *predicateFormat = [NSString stringWithFormat:#"%# == %%#",#"id"];
NSPredicate *predicate = [NSPredicate predicateWithFormat: predicateFormat, #"Joe"];
NSArray *filteredArray = [mutableArray filteredArrayUsingPredicate:predicate];
Use:
NSLog(#"user information %#", [usersInfo filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%K == '%#'",#"id",user]]]);
When you use:
NSLog(#"user information %#", [usersInfo filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K == user", #"id"]]]);"
The user will be a part of the string, it won't replace with the content of the NSString object.
In my application i have a large table of around 12000 entries. I am displaying it on tableview. But the search bar is too slow while doing dynamic search. I have read that NSPredicate method is more permorfant then NSRange.
This is my old the code:
[self.filteredListContent removeAllObjects];
listContent = [[NSArray alloc] initWithArray:[dbAccess getAllBooks]];
for (Book *book in listContent)
{
NSRange range = [book.textBook rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound)
{
[self.filteredListContent addObject:book];
}
}
My new code:
[self.filteredListContent removeAllObjects];
listContent = [[NSArray alloc] initWithArray:[dbAccess getAllBooks]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF like[c] %#",searchText];
[self.filteredListContent addObject:[listContent filteredArrayUsingPredicate:predicate]];
When i try to execute this code i received this error: "Can't do regex matching on object .'"
I would do something more like...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%k like[c] %#",propertyIAmLookingFor,searchText];
Is your book class a string? If not then you cant use SELF like. You need to substitute the name of the property you are comparing.
I need to form a predicate from multiple arrays of values. So I thought I can initially form a string with all the values, and then pass that string for usage in a predicate, i.e
NSString* stringForPredicate = [[NSString alloc] init];
if (fromDate != nil) {
stringForPredicate = [stringForPredicate stringByAppendingFormat:#"(Date > %# AND Date < %#)", fromDate, toDate];
}
There are further calculations that I do to form the final predicate string.
Now, I want the predicate to use this string. I thought that something like this would work:
NSPredicate* filterPredicate = [NSPredicate predicateWithFormat:#"%#",stringForPredicate];
But it doesnt and throws the exception:
'Unable to parse the format string "%#"'
Is there a way I can make this work?
thanks,
The stringForPredicate variable actually contains the format_string. So, you need to assign that variable in place of the format_string, and pass the args after that, seperated by commas, like this.
NSSring *stringForPredicate = #"(Date > %# AND Date < %#)";
NSPredicate* filterPredicate = [NSPredicate predicateWithFormat:stringForPredicate, fromDate, toDate];
For compound predicates:
NSMutableArray *subPredicates = [NSMutableArray array];
if (fromDate != nil) {
NSPredicate *from_predicate = [NSPredicate predicateWithFormat:#"Date > %#", fromDate];
[subPredicates addObject:from_predicate];
}
if (toDate != nil) {
NSPredicate *to_predicate = [NSPredicate predicateWithFormat:#"Date < %#", toDate];
[subPredicates addObject:to_predicate];
}
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
Wont this do it?
NSPredicate* filterPredicate = [NSPredicate predicateWithFormat:stringForPredicate];
or you have to do this
NSPredicate* filterPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%#",stringForPredicate]];
From the reference documents
Format String Summary
It is important to distinguish between
the different types of value in a
format string. Note also that single
or double quoting variables (or
substitution variable strings) will
cause %#, %K, or $variable to be
interpreted as a literal in the format
string and so will prevent any
substitution.
Try this
stringForPredicate = [stringForPredicate stringByAppendingFormat:#"(Date > %#) AND (Date < %#)", fromDate, toDate];
For those using swift:
let predicate = NSPredicate.init("Date > %# AND Date < %#", fromDate, toDate);
I am trying to learn how to use predicates and so am trying to replace the following working code with filteredArrayUsingPredicate...
[filteredLocations removeAllObjects];
for (NSString *location in locations) {
NSRange range = [location rangeOfString:query options:NSCaseInsensitiveSearch];
if (range.length > 0) {
[filteredLocations addObject:location];
}
}
Instead I am trying....
[filteredLocations removeAllObjects];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains %#", searchText];
[filteredLocations addObjectsFromArray: [locations filteredArrayUsingPredicate:predicate]];
I am not getting the same results with the predicate as I am with for loop rangeOfString. With the range of string for example searchText returns an 8 item array while with the same value returns only 2 with the predicate. Another example, hono will find honolulu in the locations array while it will not find anything using the predicate.
As I understand it SELF represents the object object being evaluated ie. the locations array, so I think that is the correct syntax.
Any help would be appreciated
Thanks, John
I thought that in your for loop, you specify an option NSCaseInsensitiveSearch but in the predicate you didn't.
You can try with this one
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(SELF contains[cd] %#)", searchText];
More details can be looked at here
NSPredicate Tutorial
Building a search with some custom objects and three scopes: All, Active, and Former. Got it working with the below code:
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString *)scope {
[[self filteredArtists] removeAllObjects];
for (HPArtist *artist in [self artistList]) {
if ([scope isEqualToString:#"All"] || [[artist status] isEqualToString:scope]) {
NSComparisonResult result = [[artist displayName] compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame) {
[[self filteredArtists] addObject:artist];
}
}
}
}
This works fine and takes scope into account. Since I wanted to search four fields at at time, this question helped me come up with the below code:
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString *)scope {
[[self filteredArtists] removeAllObjects];
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"familyName CONTAINS[cd] %# OR familyKanji CONTAINS[cd] %# OR givenName CONTAINS[cd] %# OR givenKanji CONTAINS[cd] %#", searchText, searchText, searchText, searchText];
[[self filteredArtists] addObjectsFromArray:[[self artistList] filteredArrayUsingPredicate:resultPredicate]];
}
However it no longer takes scope into account.
I have been playing around with if statements, adding AND scope == 'Active', etc. to the end of the statement and using NSCompoundPredicates to no avail. Whenever I activate a scope, I'm not getting any matches for it.
Just a note that I've seen approaches like this one that take scope into account, however they only search inside one property.
Here's about how I would do it:
NSPredicate * template = [NSPredicate predicateWithFormat:#"familyName CONTAINS[cd] $SEARCH "
#"OR familyKanji CONTAINS[cd] $SEARCH "
#"OR givenName CONTAINS[cd] $SEARCH "
#"OR givenKanji CONTAINS[cd] $SEARCH"];
- (void)filterContentForSearchText:(NSString *)search scope:(NSString *)scope {
NSPredicate * actual = [template predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:search forKey:#"SEARCH"]];
if ([scope isEqual:#"All"] == NO) {
NSPredicate * scopePredicate = [NSPredicate predicateWithFormat:#"scope == %#", scope];
actual = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:actual, scopePredicate, nil]];
}
[[self filteredArtists] setArray:[[self artistList] filteredArrayUsingPredicate:actual]];
}