NSRange Location For StringComparison - UISearchDisplayerController

Discussion in 'iOS Programming' started by Darkroom, Oct 1, 2009.

  1. Darkroom Guest

    Darkroom

    Joined:
    Dec 15, 2006
    Location:
    Montréal, Canada
    #1
    so after reviewing apple's TableSearch sample code, i was a little surprised with how weak the search is. the table lists apple products, "Powerbook, iPod touch, iPhone", etc., but the search results are only returned based on the first letter of the item. so if you search for "phone" or "touch", you receive no results, while "iph" will return "iPhone" and "iPod t" will return "iPod Touch. ghetto that!

    i assumed the cause was because NSLiteralSearch was missing from the code, but none of the search and comparison constants seem to help.

    here is the offending sample code:
    Code:
    - (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
    	{
    	[self.filteredListContent removeAllObjects]; // First clear the filtered array.
    	for (Product *product in listContent)
    		{
    		if ([scope isEqualToString:@"All"] || [product.type isEqualToString:scope])
    			{
    			NSComparisonResult result = [product.name compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
    			if (result == NSOrderedSame)
    				{
    				[self.filteredListContent addObject:product];
    				}
    			}
    		}
    	}
    
    i just know i'm missing something obvious... :rolleyes:
     
  2. Darkroom thread starter Guest

    Darkroom

    Joined:
    Dec 15, 2006
    Location:
    Montréal, Canada
    #2
    Updated:

    ok... having a difficult time wrapping my head around this. it seems that the location argument of the range is the problem here. tell me if this logic is flawed:

    the plan: after entering a character in the search field, i have to enumerate thru each string for the occurrence of that character (searchText), get the index of said character within each string and use that as the range start location while reducing the string's length by the starting character index.

    the problem: in the event of a string like @"low love" (or @"happy harry", @"Jupiter Jump", etc.) if the user tries to search for "love", the first occurrences of 'l' and 'o' will be from "low", so when the user enters the 3rd character 'v', that string will disappear from the search results as it was expecting a 'w'. how is it possible to create an accurate search method using NSComparisonResult's compare:eek:ptions:range as seen in UISearchDisplayController sample ("TableSearch")?

    i've been up all night is my only defense.
     
  3. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #3
    There are different ways that you can imagine filtering the list. You don't really mention how you want it to be filtered for your case. Most apps do a kind of anchored search, and that is what the TableSearch example does.

    The contacts app also does an anchored search. So if you type S into the search box you get all contacts that have S as the first letter of the first or last name but you don't get every contact that has an S anywhere in the name. That's what the range does in the example. It sets the comparison to the start of the strings and compares each string to the search text, but only at the beginning of the strings.

    You first need to decide what kind of comparison you want before we can tell you how to accomplish it.
     
  4. Darkroom thread starter Guest

    Darkroom

    Joined:
    Dec 15, 2006
    Location:
    Montréal, Canada
    #4
    this anchored search is what i'm trying to avoid.

    in this project i've parsed an XML file from a website and loaded one of it's elements into an array. the elements are of street intersections. there are hundreds and they look like this:

    Code:
    "Saint-Catherine / Peel"
    "Peel / Sherbrooke"
    "Amherst / Réné-Lévesque"
    "Réné-Lévesque / Peel"
    
    so the problem is that if the user wants all street intersections with Peel by entering "peel" in the searchbar, anchored searching with the above 4 examples (starting at a range location of 0) will only return "Peel / Sherbrooke" in the search results, while there are actually 3 of the 4 listed on Peel. if the user enters "catherine", i'd like the first to be returned, if the user types "levesq", i'd like the 3rd and 4th to be returned in the search results. is that called non-anchored searching?
     
  5. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #5
    Consider using NSString's rangeOfString: to give the compare: method the range you are interested in.
     
  6. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #6
    Let's just call it searching. So we have searching and anchored searching.

    Use rangeOfString: or probably one of the variants that takes options. If rangeOfString finds the string it returns a valid range. And as the docs say "Returns {NSNotFound, 0} if aString is not found." So you can easily tell if the string you searched for is found anywhere in the other string.

    Code:
    if ([product.name rangeOfString:searchString].location != NSNotFound)
         // found it
    
    One side note, I have a table with a search bar like this in my app. The strings that users may search for can contain accent marks and other unicode stuff, like the strings that you show. I allow the users to not type in the accent marks and use the diacritical insensitive search option. In fact diacriticals can be created in multiple ways and the different ways of creating them don't match as same. I've partly fixed this by normalizing my text to unicode form C but I have a feeling that I haven't hit all the edge cases. You may run into this also.
     
  7. Darkroom thread starter Guest

    Darkroom

    Joined:
    Dec 15, 2006
    Location:
    Montréal, Canada
    #7
    perfect. thanks for the help. the follow edit of apple's sample code does the trick using rangeOfString:
    Code:
    - (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
    {
    [self.filteredListContent removeAllObjects]; // First clear the filtered array.
    for (Product *product in listContent)
    	{
    	if ([scope isEqualToString:@"All"] || [product.type isEqualToString:scope])
    		{
    		[COLOR="Red"]#define kOptions (NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)[/COLOR]
    		NSComparisonResult result = [product.name compare:searchText options:[COLOR="red"]kOptions[/COLOR] range:[COLOR="Red"][product.name rangeOfString:searchText options:kOptions][/COLOR]];
    		if (result == NSOrderedSame)
    			{
    			[self.filteredListContent addObject:product];
    			}
    		}
    	}
    }
    
    not sure i follow you. do you mean that if the character É is created in unicode, than NSDiacriticInsensitiveSearch will find it, but if the character is created like É (like in HTML, don't know what it's called), than NSDiacriticInsensitiveSearch will not find it?
     
  8. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #8
    A variation of PhoneyDeveloper's version is simpler and quicker. No need to even use compare:.
     
  9. Darkroom thread starter Guest

    Darkroom

    Joined:
    Dec 15, 2006
    Location:
    Montréal, Canada
    #9
    right again! :)

    Code:
    - (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
    {
    [self.filteredListContent removeAllObjects]; // First clear the filtered array.
    for (Product *product in listContent)
    	{
    	if ([scope isEqualToString:@"All"] || [product.type isEqualToString:scope])
    [COLOR="Red"]		if ([product.name rangeOfString:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)].location != NSNotFound)
    			[self.filteredListContent addObject:product];[/COLOR]
    	}
    }
    
    it's kinda silly that apple didn't use this type of searching for their own search example, since it's stronger and more user friendly than anchored searching.
     
  10. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #10
    No. HTML doesn't have anything to do with it. Eacute can be represented in unicode in more than one way. It can be a single unicode character or it can be two characters: an e followed by a combining acute accent. The single character is preferred but it's possible for it to be the two character sequence depending on the source of the string. I am pretty sure that strings that you get from the textview's use the single character.

    If the search strings in your app use the single character then you'll probably never run into this issue. My strings were coming from a conversion routine that I didn't write and were generating the two character sequences so I ran into this problem. My solution was to make sure that my strings were passed through precomposedStringWithCanonicalMapping. If you want to torture yourself you can read about it here

    http://unicode.org/reports/tr15/

    The symptom of this will be that two strings that print out as equal will not compare as equal and will of course have different lengths.
     
  11. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #11
    One other thing. If you need to optimize this code because you're searching thousands of strings there are CF versions of the comparison functions that are a little faster than the objective C versions. Also, long loops like this are the kinds of code that benefit from using SELs and IMPs directly.
     

Share This Page