How to get all NSRange of a particular character in a NSString? - ios

I have two NSStrings: orgText and searchLetter.
I want to highlight every occurrences of the searchLetter in the orgText with a red color.
How can I get the NSRange of all occurrences of the searchLetter ?
for eg :
suppose: orgText = "abcahaiapaoiuiapplma"
searchLetter = "a".
I want to hightlight all "a" occurrences in "abcahaiapaoiuiapplma" with red color.
Thanks.

I wrote this method for my project - SUITextView with highlight:
- (NSMutableAttributedString*) setColor:(UIColor*)color word:(NSString*)word inText:(NSMutableAttributedString*)mutableAttributedString {
NSUInteger count = 0, length = [mutableAttributedString length];
NSRange range = NSMakeRange(0, length);
while(range.location != NSNotFound)
{
range = [[mutableAttributedString string] rangeOfString:word options:0 range:range];
if(range.location != NSNotFound) {
[mutableAttributedString setTextColor:color range:NSMakeRange(range.location, [word length])];
range = NSMakeRange(range.location + range.length, length - (range.location + range.length));
count++;
}
}
return mutableAttributedString;
}
And in my category of NSMutableAttributedString:
- (void) setTextColor:(UIColor*)color range:(NSRange)range {
// kCTForegroundColorAttributeName
[self removeAttribute:(NSString*)kCTForegroundColorAttributeName range:range]; // Work around for Apple leak
[self addAttribute:(NSString*)kCTForegroundColorAttributeName value:(id)color.CGColor range:range];
}

I'm not seeing any solution with regular expression, so I've created an elegant one, it may be useful for someone in the future.
- (BOOL)highlightString:(NSString *)string inText:(NSMutableAttributedString *)attributedString withColour:(UIColor *)color {
NSError *_error;
NSRegularExpression *_regexp = [NSRegularExpression regularExpressionWithPattern:string options:NSRegularExpressionCaseInsensitive error:&_error];
if (_error == nil) {
[_regexp enumerateMatchesInString:attributedString.string options:NSMatchingReportProgress range:NSMakeRange(0, attributedString.string.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
if (result.numberOfRanges > 0) {
for (int i = 0; i < result.numberOfRanges; i++) {
[attributedString addAttribute:NSBackgroundColorAttributeName value:color range:[result rangeAtIndex:i]];
}
}
}];
return TRUE;
} else {
return FALSE;
}
}

Code crash at "setTextColor" for MutableAttributeString
instead of it use below code
NSDictionary *tempdict=[NSDictionary dictionaryWithObjectsAndKeys:[UIFont boldSystemFontOfSize:12.0],NSFontAttributeName,color,NSForegroundColorAttributeName, nil];
[mutableAttributedString setAttributes:tempdict range:NSMakeRange(range.location, [word length])];

this is an easier way of doing it
NSString *str = #"hello world";
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:str];
[attr addAttributes:#{NSForegroundColorAttributeName : [UIColor redColor]}
range:[str rangeOfString:#"world"]];

Related

Unable get range from NSString for NSURL absoluteString

I'm developing the chat application, where the text entered has to be detected if its URL. If so, change its color and underline it. Please have a look at below screenshot :
To change the color of the url I've used following code:
NSString *urlString = [[detetctedURL absoluteString] stringByRemovingPercentEncoding];
/* *detetctedURL is detected url from entered text using NSDataDetector
/* *for messageText http://stackoverflow.com/somefolder/any-[thing]-etc, the detectedURL is http://stackoverflow.com/somefolder/any-%5Bthing%5B-etc */
NSRange r = [messageText rangeOfString:urlString];
if (r.location != NSNotFound)
{
//colorFromHex 4285f4
[atext addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:66.0/255.0 green:133.0/255.0 blue:244.0/255.0 alpha:1.0] range:r];
[atext addAttribute:NSUnderlineStyleAttributeName value:#(NSUnderlineStyleSingle) range:r];
//set attributed text (atext) to UILabel
}
How can I format detected URLs correctly if the messageText contains the URL with both special character and percent encoding both, or only percent encoding ?
Thanks!
Update :
With the help of following code, I was able to get the required range. However, its working almost fine for most of the links, but not all like in case if there is charcters such as ']-('.
NSString *urlString = [url absoluteString];
NSRange r = [messageText rangeOfString:urlString];
BOOL foundRange = YES;
if (r.location == NSNotFound)
{
foundRange = NO;
//for umlauts or special characters
urlString = [[url absoluteString] stringByRemovingPercentEncoding];
r = [messageText rangeOfString:urlString];
if (r.location == NSNotFound)
{
//for white space in url
urlString = [urlString stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
r = [messageText rangeOfString:urlString];
if (r.location == NSNotFound)
{
urlString = [url absoluteString];
NSString *prefix = url.scheme;
if(prefix)
{
prefix = [prefix stringByAppendingString:#"://"];
urlString = [urlString stringByReplacingOccurrencesOfString:prefix withString:#""];
r = [messageText rangeOfString:urlString];
if (r.location == NSNotFound)
{
urlString = [urlString stringByRemovingPercentEncoding];
r = [messageText rangeOfString:urlString];
if (r.location == NSNotFound)
{
urlString = [urlString stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
r = [messageText rangeOfString:urlString];
if (r.location != NSNotFound){ foundRange = YES; }
}else{ foundRange = YES; }
}else{ foundRange = YES; }
}
}else{ foundRange = YES; }
}else{ foundRange = YES; }
}
if (foundRange)
{
//colorFromHex 4285f4
[atext addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:66.0/255.0 green:133.0/255.0 blue:244.0/255.0 alpha:1.0] range:r];
[atext addAttribute:NSUnderlineStyleAttributeName value:#(NSUnderlineStyleSingle) range:r];
myLabel.attributedText = atext;
}
Use NSDataDetector to find and highlight url.
NSDataDetector* detector = [NSDataDetector dataDetectorWithTypes: NSTextCheckingTypeLink
error: nil];
NSArray* matches = [detector matchesInString: source
options: 0
range: NSMakeRange(0, [source length])];
You can use this to find phone numbers, address etc.
Set .dataDetectorTypes on your text view instead of trying to change the color yourself. The UITextView will take care of highlighting itself.

Change Colour of Small Words ('in') in UITextView using NSMutableAttributedString

I'm using NSMutableAttributedString to change the text in a UITextView as the user types. When the user types '#HELLO#' or "#TEST#" or '#test#', those strings should be red (just an example).
- (void)textViewDidChange:(UITextView *)textView
{
NSString *textViewText = textView.text;
NSMutableAttributedString * string = [[NSMutableAttributedString alloc]initWithString:textViewText];
NSString *space = #" ";
NSArray *words =[textView.text componentsSeparatedByString:space];
for (NSString *word in words) {
if ([word isEqualToString:#"#HELLO#"] || [word isEqualToString:#"#TEST#"] || [word isEqualToString:#"#test#"]) {
NSRange range=[textView.text rangeOfString:word];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range];
}
else{
NSRange range=[textView.text rangeOfString:word];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:range];
}
}
[string addAttribute:NSFontAttributeName
value:[UIFont fontWithName:#"HelveticaNeue-Light" size:20.0]
range:NSMakeRange(0, [textView.text length])];
[textView setAttributedText:string];
}
This works for almost every word, except 'in'. When I type that, 'in' is black rather than white ([UIColor whiteColor]). If I type 't', the 't' in "#test#' turns white.
I'm really confused, can somebody help me out? I thought the else part should catch these strings. Thanks.
I tried your code. I guess the issue is the range you are setting . Because it is always one word it sets color attribute of the first occurrence of that particular word . Whether it be #HELLO# or in . Try typing a particular string repeatedly separated by space you will always get the same output. I have made a few changes in your code and you can see it below . Try it out.
- (void)textViewDidChange:(UITextView *)textView
{
NSString *textViewText = textView.text;
NSLog(#"Text view Text %#" , textViewText );
NSMutableAttributedString * string = [[NSMutableAttributedString alloc]initWithString:textViewText];
NSString *space = #" ";
NSArray *words =[textView.text componentsSeparatedByString:space];
for(NSString *word in words){
NSLog(#"WORD %#" , word);
if ([word isEqualToString:#"#HELLO#"] || [word isEqualToString:#"#TEST#"] || [word isEqualToString:#"#test#"]) {
NSRange range = NSMakeRange(0, string.length);
while(range.location != NSNotFound)
{
range = [[string string] rangeOfString:word options:0 range:range];
if(range.location != NSNotFound)
{
[string addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(range.location, word.length)];
range = NSMakeRange(range.location + range.length, string.length - (range.location + range.length));
}
}
}
else{
NSRange range = NSMakeRange(0,string.length);
while(range.location != NSNotFound)
{
range = [[string string] rangeOfString:word options:0 range:range];
if(range.location != NSNotFound)
{
[string addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:range];
range = NSMakeRange(range.location + range.length, string.length - (range.location + range.length));
}
}
}
}
[string addAttribute:NSFontAttributeName
value:[UIFont fontWithName:#"HelveticaNeue-Light" size:20.0]
range:NSMakeRange(0, [textView.text length])];
[textView setAttributedText:string];
}

Replace using regular expression with replacement function

In objective-c (for ios) I want to achieve the same as I can in AS3:
var test:String = "Abba";
var reg:RegExp = /(a)|(b)/g;
var replacement:Function = function (...args):String
{
var $1:String = args[1];//matched 'a'
var $2:String = args[2];//matched 'b'
if($1)
{
//replace a with -
return "-";
}
if ($2)
{
//replace b with +
return "+";
}
return null;
}
var result:String = test.replace(reg, replacement);//A++-
trace(test, result);//Abba A++-
In other words I would like to have ability to identify which capturing group was matched and replace it accordingly, I'm looking for examples on enumerateMatchesInString: but can't find anything that can solve my problem.
enumerateMatchesInString: calls a block with an NSTextCheckingResult for each match,
and rangeAtIndex:idx gives the range of a captured subgroup:
NSString *string = #"Abba";
NSString *pattern = #"(a)|(b)";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
NSMutableString *newString = [string mutableCopy];
[regex enumerateMatchesInString:string options:0 range:NSMakeRange(0, [string length])
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange r1 = [result rangeAtIndex:1];
if (r1.location != NSNotFound) {
[newString replaceCharactersInRange:r1 withString:#"-"];
}
NSRange r2 = [result rangeAtIndex:2];
if (r2.location != NSNotFound) {
[newString replaceCharactersInRange:r2 withString:#"+"];
}
}];
NSLog(#"%#", newString);
// Output: A++-
If the replacement strings have not the same length as the original strings, then it
gets slightly more complicated, because you have to keep track of the length changes
in the resulting string:
NSMutableString *newString = [string mutableCopy];
__block int offset = 0;
[regex enumerateMatchesInString:string options:0 range:NSMakeRange(0, [string length])
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange r1 = [result rangeAtIndex:1];
if (r1.location != NSNotFound) {
r1.location += offset;
NSString *repl = #"---";
[newString replaceCharactersInRange:r1 withString:repl];
offset += [repl length] - r1.length;
}
NSRange r2 = [result rangeAtIndex:2];
if (r2.location != NSNotFound) {
r2.location += offset;
NSString *repl = #"++";
[newString replaceCharactersInRange:r2 withString:repl];
offset += [repl length] - r2.length;
}
}];
NSLog(#"%#", newString);
// Output: A++++---

Highlight particular characters in the string for all the characters of given string

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"];
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}
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];
}
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];
}
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;
}

Highlight or Underline Specific Word in a String

I would like to highlight or underline a specific set of words in a NSString. I am able to detect if the words exist, I'm just not able to get them highlighted.
NSString * wordString = [NSString stringWithFormat:#"%#", [self.myArray componentsJoinedByString:#"\n"]];
self.myLabel.text = wordString;
if ([wordString rangeOfString:#"Base Mix"].location == NSNotFound)
{
NSLog(#"string does not contain base mix");
}
else
{
NSLog(#"string contains base mix!");
NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:wordString];
NSString * editedString = [NSString stringWithFormat:#"%lu", (unsigned long)[wordString rangeOfString:#"Base Mix"].location];
NSRange theRange = NSMakeRange(0, [editedString length]);
[string beginEditing];
[string removeAttribute:NSForegroundColorAttributeName range:theRange];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:theRange];
[string endEditing];
[self.myLabel setAttributedText:string];
}
This code is closer to the right path. I do see a highlighted character, but it's the very first character in the string and not the words that I have searched for.
You can use the NSUnderlineStyleAttributeName and NSUnderlineColorAttributeName attributes. I think you can use it like this:
NSRange foundRange = [wordString rangeOfString:#"Base Mix"];
if (foundRange.location != NSNotFound)
{
[wordString beginEditing];
[wordString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:1] range:foundRange];
[wordString addAttribute:NSUnderlineColorAttributeName value:[NSColor redColor] range:foundRange];
[wordString endEditing];
}
You can use below code which relate to NSAttributed string. works only ios6+
NSString *tem = #"String with base Mix dfsdfsd ";
NSString *substring = #"base Mix";
NSRange range;
if ((range =[tem rangeOfString:substring]).location == NSNotFound)
{
NSLog(#"string does not contain base mix");
}
else
{
NSMutableAttributedString *temString=[[NSMutableAttributedString alloc]initWithString:tem];
[temString addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInt:1]
range:(NSRange){range.location,substring.length}];
NSLog(#"%#",temString);
self.yourLabel.attributedText = temString;
}
I think the part is missing from NSAttributedString. You can try with Three20
You could create an category method of NSString class like this
-(NSMutableAttributedString*)addAttributesToWords:(NSString*)string attributes:(NSArray*)attributes {
NSRange range;
if ((range = [self rangeOfString:string]).location == NSNotFound){
return [[NSMutableAttributedString alloc] initWithString:self];
}
else{
NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:self];
for (NSDictionary *attribute in attributes) {
[result addAttributes:attribute range:range];
}
return result;
}
}
How to use it::
NSArray *attributes = #[#{NSFontAttributeName : kFont_OpenSansSemiBold14},
#{NSForegroundColorAttributeName : [UIColor darkGrayColor]}];
self.label.attributedText = [#"Hello There, this is a Test" addAttributesToWords:#"Hello There" attributes:attributes];
With the help of #user1118321's answer, I wrote this function and I would hope it saves someone's time.
func highlightedString(allText:String,toBeHighlighted word:String) -> NSAttributedString{
var putInString = NSMutableAttributedString.init()
var giveFrom = NSMutableString.init(string: allText)
while true {
let range = giveFrom.range(of: word)
let index = range.location + range.length
if range.location == NSNotFound{
putInString.append(NSAttributedString.init(string: giveFrom as String))
break
}
else{
let slite = giveFrom.substring(to: index)
let highlightedSlite = NSMutableAttributedString.init(string: slite)
highlightedSlite.addAttributes([NSAttributedStringKey.backgroundColor : UIColor.yellow], range: range)
giveFrom = giveFrom.substring(from: index) as! NSMutableString
putInString.append(highlightedSlite)
}
}
return putInString
}
Maybe it is not very clear code. I welcome any suggested edits that would help.
highlight Many words
func higlighted(allText:String,words:[String]) ->NSAttributedString{
let allAttributedText = NSMutableAttributedString.init(string: allText)
var ranges = [NSRange]()
for word in words{
var string = allAttributedText.string as NSString
var i = 0
while true {
var range = string.range(of: word)
if range.location == NSNotFound {
break
}
i = i + range.location + word.count
string = string.substring(from: range.location + range.length) as NSString
range.location = i - word.count
print("\(range) XX \(word)" )
ranges.append(range)
}
}
for range in ranges{
allAttributedText.addAttributes([NSAttributedStringKey.backgroundColor : UIColor.yellow], range: range)
}
return allAttributedText
}

Resources