Add attribute for NSAttributedString with range - ios

If the line for which you want to add the attribute is at the beginning of the line, then the attribute is applied to the entire line. And if it is in another place, it works correctly.
Code:
- (void)applyStyleLinkWithRange:(NSRange)range andAttributes:(NSDictionary *)attributes {
if (NSLocationInRange(range.location + range.length, NSMakeRange(0, [self.attributedString length]))) {
NSMutableAttributedString *mutableAttributedString = [self.attributedString mutableCopy];
for (NSString *key in _activeLinkAttributes) {
[mutableAttributedString removeAttribute:key range:range];
}
for (NSString *key in _inactiveLinkAttributes) {
[mutableAttributedString removeAttribute:key range:range];
}
[mutableAttributedString addAttributes:attributes range: range];
self.attributedString = [mutableAttributedString copy];
mutableAttributedString = nil;
}}
Number is at the beginning of the line:
Number is at the other place:
How i can fix this?

Related

Different styles for links in a UITextView

I want my UITextView includes plain text, links, phone numbers and other UIDataDetectorTypes. For instance, plain text - black color, links - blue color with underline, hashtags - green color. So, I parse text and add links to hashtags:
[attributedString addAttribute:NSLinkAttributeName value:[NSString stringWithFormat:#"hashtag://%#", hashtag] range:range];
Other links are detected by default, if dataDetectorTypes set to UIDataDetectorTypeAll. But how to change link style for hashtags?
You can use the official Twitter library for tracking hashtags called twitter text along with TTTAttributedLabel.
Hashtags have special conditions, these could be more than alphanumeric characters preceded by #.
The twitter-text library will automatically find the ranges of hashtags for you then you should be able to format it with TTTAttributedLabel.
Here is a sample implementation of both libraries together.
- (void)scanForHashtag:(NSArray *)hashtags fromLabel:(id)sender
{
TTTAttributedLabel *label = (TTTAttributedLabel *)sender;
for (TwitterTextEntity *entity in hashtags) {
UIFont *boldSystemFont = [UIFont fontWithName:#"Helvetica-Bold" size:12];
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)boldSystemFont.fontName, boldSystemFont.pointSize, NULL);
NSArray *keys = [[NSArray alloc] initWithObjects:(id)kCTForegroundColorAttributeName,(id)kCTUnderlineStyleAttributeName,(NSString *)kCTFontAttributeName, nil];
UIColor *hashtagColor;
hashtagColor = [UIColor greenColor];
NSArray *objects = [[NSArray alloc] initWithObjects:hashtagColor,[NSNumber numberWithInt:kCTUnderlineStyleNone],(__bridge id)font, nil];
NSDictionary *linkAttributes = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
label.linkAttributes = linkAttributes;
NSString *hashtag = [label.text substringWithRange:entity.range];
if (entity.type == 2) { //Hashtag
[label addLinkToPhoneNumber:hashtag withRange:entity.range];
} else if (entity.type == 0) { //URL
NSURL *url = [NSURL URLWithString:[label.text substringWithRange:entity.range]];
[label addLinkToURL:url withRange:entity.range];
}
}
}
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber
{
//Do whatever you need when the hashtag is tapped.
}
[self scanForHashtag:[TwitterText hashtagsInText:#"The text that contains the tweet with hashtags." checkingURLOverlap:NO] fromLabel:yourLabel]; //yourLabel is the label that contains the text so it would be formatted.
EDIT FOR UITextView
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:str];
[attributedString addAttribute:NSBackgroundColorAttributeName
value:[UIColor yellowColor]
range:entity.range];
You need to find hashtags ranges and than add special attributes for them.
For example.
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"YOUR HASHTAG PATTERN" options:0 error:&error];
if (!error) {
[regex enumerateMatchesInString:messageText options:0 range:NSMakeRange(0, messageText.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[mutableAttributedString addAttributes:#{
NSFontAttributeName: [UIFont fontLinkMessageCell],
(__bridge NSString*) kCTForegroundColorAttributeName: [UIColor colorLinkOutCell],
NSUnderlineStyleAttributeName: #(NSUnderlineStyleNone),
} range:result.range];
}];
}
self.textView.linkTextAttributes = #{};
self.textView.textStorage.delegate = self;
- (void) textStorage: (NSTextStorage*) textStorage didProcessEditing: (NSTextStorageEditActions) editedMask range: (NSRange) editedRange changeInLength: (NSInteger) delta
{
[textStorage enumerateAttribute: NSLinkAttributeName
inRange: editedRange
options: 0
usingBlock: ^(id value, NSRange range, BOOL* stop)
{
if (value)
{
[textStorage addAttribute: NSForegroundColorAttributeName
value: [UIColor redColor]
range: range];
}
}];
}

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

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
}

Add attributes to NSMutableAttributedString character by character

Here's my situation:
I have an NSMutableAttributedString with no attributes in a text view. Whenever the user presses the backspace key, I do not want a character to be deleted, I want it to be struck through, just like the "Track Changes" feature of productivity suites. I want the user to be able to continue typing normally after that. Here's how I started out:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if (text.length == 0 && textView.text.length == 0) return YES;
if (text.length == 0 && !([[textView.text substringFromIndex:textView.text.length - 1] isEqualToString:#" "] || [[textView.text substringFromIndex:textView.text.length - 1] isEqualToString:#"\n"])) {
textView.attributedText = [self strikeText:textView.attributedText];
textView.selectedRange = NSMakeRange(textView.attributedText.length - 1, 0);
return NO;
}
return YES;
}
- (NSAttributedString *)strikeText:(NSAttributedString *)text
{
NSRange range;
NSMutableAttributedString *returnValue = [[NSMutableAttributedString alloc] initWithAttributedString:text];
if (![text attribute:NSStrikethroughStyleAttributeName atIndex:text.length - 1 effectiveRange:&range]) {
NSLog(#"%#", NSStringFromRange(range));
NSLog(#"%#", [text attribute:NSStrikethroughStyleAttributeName atIndex:text.length - 1 effectiveRange:&range]);
[returnValue addAttribute:NSStrikethroughStyleAttributeName value:#(NSUnderlineStyleSingle) range:NSMakeRange(text.length - 1, 1)];
[returnValue addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(text.length - 1, 1)];
}
else {
[returnValue addAttribute:NSStrikethroughStyleAttributeName value:#(NSUnderlineStyleSingle) range:NSMakeRange(range.location - 1, 1)];
[returnValue addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(range.location - 1, 1)];
}
[returnValue removeAttribute:NSStrikethroughStyleAttributeName range:NSMakeRange(returnValue.length, 1)];
return returnValue;
}
However, no matter how hard I think, I can't wrap my head around the situation. This code doesn't work, or works partially. The value returned by attribute: atIndex: effectiveRange: is always nil, doesn't matter if the attribute actually exists or not. The effective range is out of bounds of the text I have.
Please help me out here.
In your strikeText: method you're only checking the very end of your attributedString. If you want to check the last character you should check from text.length -2, assuming that text is long enough. Also you're removeAttribute in the end of the method does not make much sense too me.
A simple approach on how you can reuse the range from the Delegate-Protocol to strike only the characters you need:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
// check if your replacement is going to be empty, therefor deleting
if ([text length] == 0) {
// don't strike spaces and newlines, could use NSCharacterSet here
NSString *textToDelete = [textView.text substringWithRange:range];
if (![#[ #" ", #"\n" ] containsObject:textToDelete]) {
textView.attributedText = [self textByStrikingText:textView.attributedText inRange:range];
}
textView.selectedRange = NSMakeRange(range.location, 0);
return NO;
}
return YES;
}
- (NSAttributedString *)textByStrikingText:(NSAttributedString *)text inRange:(NSRange)range
{
NSMutableAttributedString *strickenText = [[NSMutableAttributedString alloc] initWithAttributedString:text];
[strickenText addAttribute:NSStrikethroughStyleAttributeName value:#(NSUnderlineStyleSingle) range:range];
[strickenText addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range];
return strickenText;
}
There might be more edge cases but this is a simple approach that does what you want.
You should try:
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:#"Hello. That is a test"];
[str addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:22] range:NSMakeRange(2,1)];
[str addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(8,2)];
[str addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(18,2)];
And attributes are described here:
https://developer.apple.com/documentation/foundation/nsattributedstringkey?language=objc

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

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

Resources