I want to know how searching of a string works in objective C when the iphone application has to support multiple language.
Assuming I have a search function that looks like this currently:
- (int)showSearchResultForQuery:(NSString *)query
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name CONTAINS[cd] %# || address CONTAINS[cd] %#",query, query];
storesFiltered = [[NSMutableArray alloc]initWithArray:[stores filteredArrayUsingPredicate:predicate]];
int count = [storesFiltered count];
if(count > 0)
{
// we have some results
[resultTable reloadData];
}
return count;
}
This piece of code basically accepts a query string and update an array being used by a table using NSPredicate. I want to know, what do i need to take in consideration, if this function has to accepts multiple languages? chinese. english...japanese... will this function still work?
Thanks.
The only thing you need to consider is that the encoding of the strings in the array will be the same as the encoding of the query string.
For example, chinese is sometimes represented in UTF16, so you need to make sure that both the strings in the array and query string are encoded in UTF16.
Everything else will work out of the box.
Related
I'm setting the string of my predicate like this:
[NSString stringWithFormat:#"(name like '%#')",name]
but if name contains ' characters, for example if name is "family's" it crashes.
How can I fix this?
You don't need the ' in NSPredicate. They are being inserted automatically.
Just try
[NSPredicate predicateWithFormat:#"(name like %#)",name];
The reason this crashed was because the predicate value was interrupted.
If you create a string with format, like in your example, your going to end up with (name like 'family's'), which obviously can't work.
If you use predicateWithFormat: on the other hand, you can let it handle this itself. It will escape your special characters.
If you don't want/aren't able to use predicateWithFormat, then you can simply replace any apostrophes in your string.
I do this in my app, when checking my UISearchBar control, to see if the user's trying to look for particular CoreData records:
NSString* searchString = self.searchBar.text;
if (searchString.length != 0)
{
searchString = [searchString stringByReplacingOccurrencesOfString:#"'" withString:#"\\'"];
NSString* filter = [NSString stringWithFormat:#"companyName CONTAINS[cd] '%#'", searchString];
NSPredicate* predicate1 = [NSPredicate predicateWithFormat:filter];
// ...etc...
}
(This is a simplified version of a generic function I use, which actually searches CoreData using a variable number of filter strings, which is why I don't use predicateWithFormat directly.)
I have implemented a UISearchDisplayController that allows users to search a table. Currently the predicate I am using to search is as follows,
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"Name contains[cd] %#", searchText];
Now lets say a users searches for "beans, cooked" the corresponding matches are found in the table. But if the user enters the search text as "beans cooked" without the comma, there will be no matches found.
How can I re-write my predicate to "ignore" the commas when searching? In other words how can I re-write it so that it views "beans, cooked" being equal to "beans cooked" (NO COMMMA)?
First a disclaimer:
I think that what you are trying to do is to add some "fuzzyness" to your search algorithm, seen that you want to make your match insensitive to certain differences in user input.
Predicates (which are logic constructs) are by their very nature not fuzzy, so there is an underlying impedance mismatch between the problem and the tool chosen.
Anyway, one way to go about it could be to add a method to your model object class.
In this method, you can clean your name string so it only contains the most basic characters, say numbers, ascii letters and a space.
Being totally deterministic, such a method is effectively a read-only string property on your object, and as such it can be used to match in predicates.
Here is an implementation that removes punctuation, accents and diacritics:
- (NSString *)simplifiedName
{
// First convert the name string to a pure ASCII string
NSData *asciiData = [self.name dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *asciiString = [[[NSString alloc] initWithData:asciiData encoding:NSASCIIStringEncoding] lowercaseString];
// Define the characters that we will allow in our simplified name
NSString *searchCharacters = #"0123456789 abcdefghijklmnopqrstuvwxyz";
// Remove anything else
NSString *regExPattern = [NSString stringWithFormat:#"[^%#]", searchCharacters];
NSString *simplifiedName = [asciiString stringByReplacingOccurrencesOfString:regExPattern withString:#"" options:NSRegularExpressionSearch range:NSMakeRange(0, asciiString.length)];
return simplifiedName;
}
Now, a predicate could be made to search in the simplified name:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"self.simplifiedName = %#", searchString];
You would of course want to clean the search string using the same algorithm used to clean the name, so it would probably be a good idea to factor it out into a general method to be used in both places.
Last, the simplifiedName method can also be added by implementing a category to the model object class so you don't have to modify its code, which is handy in case your object class is defined in an auto-generated file by Core Data.
This may be a bit hacky, but you could just remove the comma from the search term.
Example:
searchText = [searchText stringByReplacingOccurrencesOfString:#"," withString:#""];
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"Name contains[cd] %#", searchText];
The best solution I found for this type of problem is to actually add an entry in each items dictionary that has the same name but will all punctuations, commas, dashes, etc. removed like in this answer
All,
I am trying to use predicates to bring back a search return, giving precedence to strings that start with the search string VS. simply contained within it.
For example if the search string was "Objective-C", I want to get the filtered results back like this:
Objective-C a Primer
Objective-C Patterns
Objective-C Programming
All About Objective-C
How to program in Objective-C
Here is what I tried but since it's an OR, it clearly does not give precedence to the first condition. Is there a way to do a type of "chaining" with predicates? Thanks
NSPredicate *filter = [NSPredicate predicateWithFormat:#"subject BEGINSWITH [cd] %# OR subject CONTAINS [cd]", searchText,searchText];
NSArray *filtered = [myArray filteredArrayUsingPredicate: filter];
I don't think there's a way to do with NSPredicate by itself. What you seem to be asking for is sorted results. You do that by sorting the results after you get them back from the predicate. In this case, since the sort order isn't simply alphabetical, you should use NSArray's -sortedArrayUsingComparator: method. Something like this (not tested, typed off the top of my head).
NSArray *sortedStrings = [filtered sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *string1 = (NSString *)obj1;
NSString *string2 = (NSString *)obj2;
NSUInteger searchStringLocation1 = [string1 rangeOfString:searchString].location;
NSUInteger searchStringLocation2 = [string2 rangeOfString:searchString].location;
if (searchStringLocation1 < searchStringLocation2) return NSOrderedDescending;
if (searchStringLocation1 > searchStringLocation2) return NSOrderedAscending;
return NSOrderedSame;
}];
It looks like you're trying to sort your array using a predicate, or else trying to filter AND sort using just a predicate. Predicates return boolean values: either a string begins with or contains the search text, or it doesn't. Predicates don't tell you anything about relative order. Filtering an array removes those objects for which the predicate you supply returns NO, so only objects beginning with or containing the search text will be present in the filtered array. Indeed, since any string that begins with the search text also contains it, you could simplify your predicate to just the 'contains' part.
If you want to change the order of your filtered results, sort the filtered array with an appropriate comparator.
I'm trying to use NSPredicate with 64-bit numbers, but for some reason it's failing. I create a predicate with the following string:
[NSString stringWithFormat:#"UID == %qu", theUID]
And it always fails. Yet if I loop through my objects and compare if ( UID == theUID ) I find one that is fine. I'm really confused as to why this isn't working correctly.
Does the above look correct? My predicates work fine for other integers or even strings. But this seems to be failing.
Thanks!
Edit: So strange... so I create my predicate by doing:
NSPredicate *myPredicate = [NSPredicate predicateWithFormat:myString];
Now when I print myString, and then print myPredicate (by doing NSLog(#"%#", blah);) I get:
String: UID == 17667815990388404861 Predicate: UID ==
9223372036854775807
It's the same string, why are these different?
Possibly theUID is not an unsigned value?
A more robust way to build predicates would be to pass in an NSNumber:
NSNumber *theUIDNum = [NSNumber numberWithUnsignedLongLong:theUID];
[NSString stringWithFormat:#"UID == %#", theUID]
This gives you the bonus ability to debug and print out the value of theUINum to make sure it got the transition right.
In your NSPredicate you can use something like this
let myNSNumber = NSNumber(value: theUID)
let predicate = NSPredicate(format: "UID = %i", myNSNumber.int64Value)
Is there any way to do this? I have a set of items that I want to exclude from another set. I know I could loop through each item in my set and only add it to my filteredSet if it's not in the other set, but it would be nice if I could use a predicate.
The set of items to exclude isn't a set of the same type of object directly; it's a set of strings; and I want to exclude anything from my first set if one of the attributes matches that string.... in other words:
NSMutableArray *filteredArray = [NSMutableArray arrayWithCapacity:self.questionChoices.count];
BOOL found;
for (QuestionChoice *questionChoice in self.questionChoices)
{
found = NO;
for (Answer *answer in self.answers)
{
if ([answer.units isEqualToString:questionChoice.code])
{
found = YES;
break;
}
}
if (!found)
[filteredArray addObject:questionChoice];
}
Can this be done with a predicate instead?
This predicate format string should work:
#"NONE %#.units == code", self.answers
Combine it with the appropriate NSArray filtering method. If self.questions is a regular immutable NSArray, it would look something like
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"NONE %#.units == code", self.answers]
NSArray *results = [self.questions filteredArrayUsingPredicate:predicate];
If it's an NSMutableArray, the appropriate usage would be
[self.questions filterUsingPredicate:predicate];
Be careful with that last one though, it modifies the existing array to fit the result. You can create a copy of the array and filter the copy to avoid that, if you need to.
Reference:
NSArray Class Reference
NSMutableArray Class Reference
Predicate Programming Guide
Check out the example given by Apple for using predicates with arrays. It employs filteredArrayUsingPredicate.