4

Particular characters are to be highlighted in red color on the label so I wrote below function which works well, but I want to confirm, is there any other efficient way of doing this ? e.g.

-(NSMutableAttributedString*)getAttributeText:(NSString*)string forSubstring:(NSString*)searchstring {
    NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:_lblName.text];
    NSRange searchRange = NSMakeRange(0,string.length);
    for (NSInteger charIdx=0; charIdx<searchstring.length; charIdx++){
        NSString *substring = [searchstring substringWithRange:NSMakeRange(charIdx, 1)];
        NSRange foundRange;
        searchRange.location = 0;
        while (searchRange.location < string.length) {
            searchRange.length = string.length-searchRange.location;
            foundRange = [string rangeOfString:substring options:1 range:searchRange];
            [text addAttribute: NSForegroundColorAttributeName value: [UIColor redColor] range:foundRange];
            if (foundRange.location != NSNotFound) {
                searchRange.location = foundRange.location+foundRange.length;
            } else {
                // no more substring to find
                break;
            }
        }
    }
    return text;
}

Below is the code how I use it, and result as well

NSString *string = @"James Bond Always Rocks";
_lblName.text = string;
_lblAttributedName.attributedText = [self getAttributeText:string forSubstring:@"ao"];

enter image description here

Update

NSString *string = @"James Bond Always Rocks";    
NSRange range = [string rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"J"] options:NSCaseInsensitiveSearch];
NSLog(@"range->%@",NSStringFromRange(range)); //This prints range->{0, 1}

NSString *string = @"James Bond Always Rocks";    
NSRange range = [string rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"j"] options:NSCaseInsensitiveSearch];
NSLog(@"range->%@",NSStringFromRange(range)); //This prints range->{2147483647, 0}
Janak Nirmal
  • 22,706
  • 18
  • 63
  • 99
  • Similar to [this](http://stackoverflow.com/questions/14231879/is-it-possible-to-change-color-of-single-word-in-uitextview-and-uitextfield/14231900#14231900) – Anoop Vaidya Mar 14 '14 at 08:56
  • Not really, as I am asking about performance and highlighting each character in the given string. +1 though :) – Janak Nirmal Mar 14 '14 at 09:00
  • [`NSScanner`](https://developer.apple.com/library/Mac/documentation/Cocoa/Reference/Foundation/Classes/NSScanner_Class/Reference/Reference.html) maybe? – Guillaume Algis Mar 14 '14 at 09:04

3 Answers3

6

You can simplify it by searching for a pattern ("[ao]+" in your example) to eliminate the outer loop:

NSString *string = @"James Bond Always Rocks";
NSString *searchstring = @"ao";
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:string];

// A regular expression pattern that matches a sequence of the characters in "searchString":
NSString *pattern = [NSString stringWithFormat:@"[%@]+", [NSRegularExpression escapedPatternForString:searchstring]];

NSRange foundRange = [string rangeOfString:pattern options:NSRegularExpressionSearch|NSCaseInsensitiveSearch];
while (foundRange.location != NSNotFound) {
    [text addAttribute: NSForegroundColorAttributeName value: [UIColor redColor] range:foundRange];
    NSRange nextRange = NSMakeRange(foundRange.location + foundRange.length, string.length - foundRange.location - foundRange.length);
    foundRange = [string rangeOfString:pattern options:NSRegularExpressionSearch|NSCaseInsensitiveSearch range:nextRange];
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
4

Here is my version, this is very basic approach using simple loops.

Why I posted it because I tracked the efficiency and please see the time taken by each of the implementations.

2014-03-14 15:48:42.792 TimeEfficiency[1166:303] My: 0.000073 seconds

2014-03-14 15:48:45.319 TimeEfficiency[1166:303] martin: 0.000278 seconds

2014-03-14 15:48:48.263 TimeEfficiency[1166:303] avt: 0.000029 seconds

2014-03-14 15:48:51.152 TimeEfficiency[1166:303] janak: 0.000092 seconds

Hence Avt's is best in time-performance.


NSString *string = @"James Bond Always Rocks";
NSString *searchstring = @"ao";

NSMutableArray *characters = [NSMutableArray new];
for (NSInteger i=0; i<searchstring.length; i++) {
    [characters addObject:[searchstring substringWithRange:NSMakeRange(i, 1)]]; //ao
}
//store all the location of each of the char
NSMutableArray *locations = [NSMutableArray new];
for (NSInteger i=0; i<string.length; i++) {
    if ([characters containsObject: [string substringWithRange:NSMakeRange(i, 1)]] ){
        [locations addObject:@(i)];
    }
}

//loop for string and for each location change the color

NSMutableAttributedString *text = [[NSMutableAttributedString alloc]initWithString:string];
for (NSInteger i=0; i<locations.count; i++) {
    NSRange range=NSMakeRange([locations[i] intValue], 1);
    [text addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range];
}
Anoop Vaidya
  • 46,283
  • 15
  • 111
  • 140
2

My variant with NSCharacterSet

- (NSMutableAttributedString*)getAttributeText:(NSString*)string forSubstring:(NSString*)searchstring {
    NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:string];
    NSRange searchRange = NSMakeRange(0,string.length);
    NSString *allCaseString = [[searchstring uppercaseString] stringByAppendingString:[searchstring lowercaseString]];
    NSCharacterSet *chSet = [NSCharacterSet characterSetWithCharactersInString:allCaseString];
    NSRange foundRange;

    while (foundRange = [string rangeOfCharacterFromSet:chSet options:NSCaseInsensitiveSearch range:searchRange],
           foundRange.location != NSNotFound) {
        [text addAttribute: NSForegroundColorAttributeName value: [UIColor redColor] range:foundRange];
        NSUInteger newStart = foundRange.location + foundRange.length;
        searchRange = (NSRange){newStart, string.length - newStart};
    }
    return text;
}
Avt
  • 16,927
  • 4
  • 52
  • 72
  • Yes, `rangeOfCharacterFromSet` is probably better. Hadn't thought of that method. – Martin R Mar 14 '14 at 09:49
  • I have tried this, but it doesn't ignore cases e.g. If I search for "go" it won't work, but if I search "Jao" than its working. Any ideas ? Though that can be resolved by lowering main string and search string both for comparison only but this should work as you have added `NSCaseInsensitiveSearch` – Janak Nirmal Mar 14 '14 at 11:15
  • Can you provide me with test string and substring? – Avt Mar 14 '14 at 11:55
  • I have checked - and yes. It seems that NSCaseInsensitiveSearch not works. I am confused. I have opened additional question for it http://stackoverflow.com/questions/22406123/nscaseinsensitivesearch-not-works-in-rangeofcharacterfromset – Avt Mar 14 '14 at 13:18
  • OK, in docs its written that this is normal behaviour for rangeOfCharacterFromSet. I have updated the answer. Now iti case insensitive. Please check. – Avt Mar 14 '14 at 14:07