I am currently using the following algorithm to search on my iPhone app:
NSRange range = [entry.englishEntry rangeOfString:searchText options:NSCaseInsensitiveSearch];
if(range.location != NSNotFound)
{
[self.filteredListContent addObject:entry];
}
The problem is that when I search for a word like 'crap' I also get results for words like 'scrap' which is irrelevant. I am unfamiliar with NSRange so what is the search algorithm for searching the whole word?
I just solved this problem by adding a simple category on NSString to do a word boundary search. Here's the code:
#interface NSString (FullWordSearch)
// Search for a complete word. Does not match substrings of words. Requires fullWord be present
// and no surrounding alphanumeric characters.
- (BOOL)containsFullWord:(NSString *)fullWord;
#end
#implementation NSString (FullWordSearch)
- (BOOL)containsFullWord:(NSString *)fullWord {
NSRange result = [self rangeOfString:fullWord];
if (result.length > 0) {
if (result.location > 0 && [[NSCharacterSet alphanumericCharacterSet] characterIsMember:[self characterAtIndex:result.location - 1]]) {
// Preceding character is alphanumeric
return NO;
}
if (result.location + result.length < [self length] && [[NSCharacterSet alphanumericCharacterSet] characterIsMember:[self characterAtIndex:result.location + result.length]]) {
// Trailing character is alphanumeric
return NO;
}
return YES;
}
return NO;
}
#end
Yes you can search within words. You need to split the string into components first. Then loop through each one and compare them.
Something like that:
NSArray *words = [entry.english componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
for (NSString *word in words)
{
NSComparisonResult result = [word compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame)
{
[self.filteredListContent addObject:entry];
break;
}
}
Instead of finding the range of a string, just do a case-insensitive compare and check if the result is NSOrderedSame
if([entry.english caseInsensitiveCompare:searchText] == NSOrderedSame){
[self.filteredListContent addObject:entry];
}
This will compare the text with the whole word and not just look for the range.
Now i make it as more generic, by using this code you can search any string between target string
NSString * strName =[entry.english lowercaseString];
if ([strName rangeOfString:[searchText lowercaseString]].location != NSNotFound) {
[self.filteredListContent addObject:entry];}
Related
Can someone tell me why this evaluates every time to true?!
The input is: jkhkjhkj. It doesn't matter what I type into the phone field. It's every time true...
NSRange range = NSMakeRange (0, [phone length]);
NSTextCheckingResult *match = [NSTextCheckingResult phoneNumberCheckingResultWithRange:range phoneNumber:phone];
if ([match resultType] == NSTextCheckingTypePhoneNumber)
{
return YES;
}
else
{
return NO;
}
Here is the value of match:
(NSTextCheckingResult *) $4 = 0x0ab3ba30 <NSPhoneNumberCheckingResult: 0xab3ba30>{0, 8}{jkhkjhkj}
I was using RegEx and NSPredicate but I've read that since iOS4 it's recommended to use NSTextCheckingResult but I can't find any good tutorials or examples on this.
Thanks in advance!
You are using the class incorrectly. NSTextCheckingResult is the result of a text checking that is done by NSDataDetector or NSRegularExpression. Use NSDataDetector instead:
NSError *error = NULL;
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypePhoneNumber error:&error];
NSRange inputRange = NSMakeRange(0, [phone length]);
NSArray *matches = [detector matchesInString:phone options:0 range:inputRange];
// no match at all
if ([matches count] == 0) {
return NO;
}
// found match but we need to check if it matched the whole string
NSTextCheckingResult *result = (NSTextCheckingResult *)[matches objectAtIndex:0];
if ([result resultType] == NSTextCheckingTypePhoneNumber && result.range.location == inputRange.location && result.range.length == inputRange.length) {
// it matched the whole string
return YES;
}
else {
// it only matched partial string
return NO;
}
I have to search a word in an array using UISearchBar. for example i have "Chicken Salad" string in an array at index 2. now if want to search it using last word "Salad" then what should i do?
for the time i am using this code and it is working good but if you search from first word.
(appDelegate.isAirlineSelection == YES) {
for (int i = 0; i < [menuArray count]; i++) {
if ([[[[menuArray objectAtIndex:i] itemName] uppercaseString] hasPrefix:search]||[[[[menuArray objectAtIndex:i] itemName] uppercaseString] hasSuffix:search]) {
[tableMenuArray addObject:[menuArray objectAtIndex:i]];
NSLog(#"Found");
}
}
Kindly help me
try this way, It'll search even between the words:
for(NSString *name in menuArray)
{
if ([name rangeOfString:search options:NSLiteralSearch|NSCaseInsensitiveSearch].location != NSNotFound)
[tableMenuArray addObject:name];
NSLog(#"Found");
}
First you should break your array name in two parts and create two arrays, then you can easily use search bar for second array and can find exact result ,what do you want.
for (NSString *name in lastnames)
{
NSComparisonResult result = [name compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame)
{
[searchedNames addObject:name];
}
}
i have text message and I want to check whether it is containing text "http" or URL exists in that.
How will I check it?
NSString *string = #"xxx http://someaddress.com";
NSString *substring = #"http:";
Case sensitive example:
NSRange textRange = [string rangeOfString:substring];
if(textRange.location != NSNotFound){
//Does contain the substring
}else{
//Does not contain the substring
}
Case insensitive example:
NSRange textRange = [[string lowercaseString] rangeOfString:[substring lowercaseString]];
if(textRange.location != NSNotFound){
//Does contain the substring
}else{
//Does not contain the substring
}
#Cyprian offers a good option.
You could also consider using a NSRegularExpression which would give you far more flexibility assuming that's what you need, e.g. if you wanted to match http:// and https://.
Url usually has http or https in it
You can use your custom method containsString to check for those strings.
- (BOOL)containsString:(NSString *)string {
return [self containsString:string caseSensitive:NO];
}
- (BOOL)containsString:(NSString*)string caseSensitive:(BOOL)caseSensitive {
BOOL contains = NO;
if (![NSString isNilOrEmpty:self] && ![NSString isNilOrEmpty:string]) {
NSRange range;
if (!caseSensitive) {
range = [self rangeOfString:string options:NSCaseInsensitiveSearch];
} else {
range = [self rangeOfString:string];
}
contains = (range.location != NSNotFound);
}
return contains;
}
Example :
[yourString containsString:#"http"]
[yourString containsString:#"https"]
I am using NSComparisonResult with my SearchController:
for (Annotation *ano in listContent) {
NSComparisonResult result = [ano.title compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame) {
[self.filteredListContent addObject:ano];
}
}
If I search for a string it will only find the result if it starts with that string.
Record is "My Art Gallery"
Search for "My Art Gallery" <---
Found
Search for "My " <--- Found
Search for "Art" <--- Not Found
Search for "Gallery" <--- Not found
How can I change my code so that I can find parts of the string as I showed above?
I ended up using NSRange which allowed me to basically search for a substring:
for (Annotation *ano in listContent) {
NSRange range = [ano.title rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
[self.filteredListContent addObject:ano];
}
}
There is a straight forward way to do a substring check:
NSComparisonResult result1 = [dummyString compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:[dummyString rangeOfString:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)]];
if (result1 == NSOrderedSame)
{
[self.filteredListContent addObject:dummyString]; // this can vary drastically
}
NOTE: I am using this with a UISearchDisplayController and this worked perfectly for me.
Hope this helps you in future.
Thanks to this post (UPDATE: This link doesn't work anymore.)
I'm developing a custom UIViewController for iPhone that emulates a subset of the MPMediaPickerController for files in my application's local documents directory. In particular, I'm attempting to re-create the Songs tab. I've been successful in creating my new controller, except I can't get the song titles to sort like they do in the iPod Library or the MPMediaPickerController. Here's an example of how the song names need to be sorted:
Awesome Song Title
Cool Song
The Darkest Song Ever
My Song Title
A Really Cool Song
Why Me?
4 Hours Wasted
As you can see, the sorting excludes the leading articles in the song titles and also places songs that begin with a numeric value at the end of the list. Can anyone suggest an efficient sorting function that takes these tules into account?
Since it looks like no one could offer up a solution, I thought I would post the solution I came up with. First, I created a model for my data:
#interface MyModel : NSObject
{
NSString* _value;
NSString* _sortableValue;
}
#property (nonatomic,copy) NSString* value;
- (NSString*)sortableValue;
- (NSString*)comparableString:(NSString*)str;
#end
They key to the model is the comparableString method, which gets used to create the sortableValue. Here's the implementation of the model:
#implementation MyModel
#synthesize value=_value;
-(void)dealloc
{
[_value release];
[_sortableValue release];
[super dealloc];
}
- (void)setValue:(NSString*)value
{
[_value release];
_value = [value copy];
[_sortableValue release];
_sortableTValue = nil;
}
- (NSString*)sortableValue
{
if (_sortableValue == nil)
_sortableValue = [[self comparableString:_value] retain];
return _sortableValue;
}
- (NSString*)comparableString:(NSString*)str
{
if (str == nil)
return nil;
else if ([str length] == 0)
return [NSString stringWithString:str];
NSCharacterSet* numbersSet = [NSCharacterSet decimalDigitCharacterSet];
if ([str rangeOfCharacterFromSet:numbersSet options:0 range:NSMakeRange(0, 1)].location != NSNotFound)
return [NSString stringWithString:str];
NSRange range = NSMakeRange(0, [str length]);
if ([str compare:#"a " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 2)] == NSOrderedSame)
range.location = 2;
else if ([str compare:#"an " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 3)] == NSOrderedSame)
range.location = 3;
else if ([str compare:#"the " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 4)] == NSOrderedSame)
range.location = 4;
range.length -= range.location;
NSCharacterSet* lettersSet = [NSCharacterSet letterCharacterSet];
NSUInteger letterOffset = [str rangeOfCharacterFromSet:lettersSet options:0 range:range].location;
if (letterOffset == NSNotFound)
return [NSString stringWithString:str];
letterOffset -= range.location;
range.location += letterOffset;
range.length -= letterOffset;
return [str substringWithRange:range];
}
#end
In addition to the removing the leading articles from the string, it also removes any leading non-letter characters. I have a song in my iPod library entitled "$ell Your $oul", which ends up in the E section in the MPMediaPickerController. I'm not sure that's what I would have done had I crated the initial sorting algorithm, but I was going to consistency with the MPMediaPickerController, so there you go.
The final piece of the puzzle is the UILocalizedIndexedCollation class. This handy little helper class will help you sort your data to make supplying it to a UITableView via a UITableViewDataSource a piece of cake. Here's a snippet on how to use the UILocalizedIndexedCollation class in conjunction with the model:
// tableData will contain an NSArray for each populated section in the table view
NSMutableDictionary* tableData = [NSMutableDictionary dictionary];
NSMutableArray* myArray = [NSMutableArray array];
// Populate myArray with instances of MyModel
UILocalizedIndexedCollation* indexer = [UILocalizedIndexedCollation currentCollation];
for (MyModel* data in myArray)
{
NSInteger index = [indexer sectionForObject:data collationStringSelector:#selector(sortableValue)];
NSNumber* key = [[NSNumber alloc] initWithInteger:index];
NSMutableArray* array = [tableData objectForKey:key];
if (array == nil)
{
array = [NSMutableArray new]; // Will be released after creating a sorted array in the following section
[tableData setObject:array forKey:key];
}
[array addObject:data];
[key release];
}
[tableData enumerateKeysAndObjectsUsingBlock:^(id key, id array, BOOL* stop)
{
NSMutableArray* sortedArray = [[indexer sortedArrayFromArray:array collationStringSelector:#selector(sortableValue)] mutableCopy];
[tableData setObject:sortedArray forKey:key];
[array release];
}];
One quick note about UILocalizedIndexedCollation (from Apple's documentation):
If the application provides a
Localizable.strings file for the
current language preference, the
indexed-collation object localizes
each string returned by the method
identified by selector.
So make sure you provide a Localizable.strings for each language you want to support, or your table view will only have sections A-Z and #.
It took me a while to work out all of the details on this, so I hope it becomes useful for other people. If you see any ways I can improve this, please let me know!
You may need to consider certain characters with accents as well such as è, é, ò, à, ù, ì.
So I slightly modified your code to incorporate this. Your code is a great contribution to all of us iphone developers
- (NSString*)comparableString:(NSString*)str
{
if (str == nil)
return nil;
else if ([str length] == 0)
return [NSString stringWithString:str];
NSCharacterSet* numbersSet = [NSCharacterSet decimalDigitCharacterSet];
if ([str rangeOfCharacterFromSet:numbersSet options:0 range:NSMakeRange(0, 1)].location != NSNotFound)
return [NSString stringWithString:str];
NSRange range = NSMakeRange(0, [str length]);
if ([str compare:#"a " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 2)] == NSOrderedSame)
range.location = 2;
else if ([str compare:#"an " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 3)] == NSOrderedSame)
range.location = 3;
else if ([str compare:#"the " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 4)] == NSOrderedSame)
range.location = 4;
range.length -= range.location;
NSCharacterSet* lettersSet = [NSCharacterSet letterCharacterSet];
NSUInteger letterOffset = [str rangeOfCharacterFromSet:lettersSet options:0 range:range].location;
if (letterOffset == NSNotFound)
return [NSString stringWithString:str];
letterOffset -= range.location;
range.location += letterOffset;
range.length -= letterOffset;
//my modification starts here.........
NSString * finalString = [str substringWithRange:range];
NSString * firstCharString = [finalString substringToIndex:1];
NSData * encodedData = [firstCharString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString * encodedString = [[NSString alloc] initWithBytes:[encodedData bytes] length:[encodedData length] encoding:NSASCIIStringEncoding];
if ([encodedString isEqualToString:#"?"]) {
return finalString;
}
NSString * finalProcessedString = [finalString stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:encodedString];
[encodedString release];
return finalProcessedString;
}