Add attributes to NSMutableAttributedString character by character - ios

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

Related

Add attribute for NSAttributedString with range

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?

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

UILabel with Multiple links

Hello I am trying to tappable UILabel similar to Facebook's like text, Label's text would be similar to
"You, Steve and 50 others like this."
Where "You", "Steve" and "50 others" should be tappable separately.
I am trying my luck with NSAttributedString but it is not helping me, Can anyone help me to find a way ?
try this, but it's not for label but it's for textView.
NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:#"firstsecond"];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0,5)];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(5,6)];
//[string addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:10] range:NSMakeRange(0,5)];
[string addAttribute:NSLinkAttributeName value:[NSURL URLWithString:#"http://www.google.co.in"] range:NSMakeRange(0,5)];
[string addAttribute:NSLinkAttributeName value:[NSURL URLWithString:#"http://www.yahoo.com"] range:NSMakeRange(5,6)];
self.txtView.attributedText=string;
self.txtView.scrollEnabled = NO;
self.txtView.editable = NO;
self.txtView.textContainer.lineFragmentPadding = 0;
self.txtView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);
self.txtView.delegate = self;
}
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange: (NSRange)characterRange
{
return YES;
}

How to change the color of Text on tap in Textview

I need to change the text color on tap in UITextView. I got NSRange on selected text of text view but unable to change its color using this code.
[mutableAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:10.0/255.0 green:15.0/255.0 blue:5.0/255.0 alpha:1.0] range:range1];
Is there any way to change tap color change in text view?
I hope, This code may help you :)
In my scenario i am changing selected textColor by Tapping uiButton, You can try this way.
// When text is tapped or selected by user we can change the
- (IBAction)applyBlueColor:(id)sender {
// getting textRange
NSRange textRange = [_textView selectedRange];
NSDictionary *attributeDictionary = [_textView.textStorage attributesAtIndex:textRange.location
effectiveRange:nil];
if ([attributeDictionary objectForKey:NSForegroundColorAttributeName] == nil ||
[attributeDictionary objectForKey:NSForegroundColorAttributeName] != [UIColor blackColor]) {
// Setting blue color to my selected text.
NSDictionary *colorDictionary = #{NSForegroundColorAttributeName: [UIColor blueColor]};
[_textView.textStorage beginEditing];
[_textView.textStorage setAttributes:colorDictionary range:textRange];
[_textView.textStorage endEditing];
}
}
hello every one i have post my answer. i hope it will help to other,enter code here
-(void)textTapped:(UITapGestureRecognizer *)recognizer
{
NSMutableAttributedString *attributedStringText = [[NSMutableAttributedString alloc]initWithString:txtView.text];
UITextView *textView = (UITextView *)recognizer.view;
NSLayoutManager *layoutManager = textView.layoutManager;
CGPoint location = [recognizer locationInView:textView];
location.x -= textView.textContainerInset.left;
location.y -= textView.textContainerInset.top;
// Get character Index.
NSInteger characterIndex = [layoutManager characterIndexForPoint:location inTextContainer:textView.textContainer fractionOfDistanceBetweenInsertionPoints:NULL];
if (characterIndex < textView.textStorage.length)
{
// Enumerate string and get word from character index.
[txtView.text enumerateSubstringsInRange:NSMakeRange(0, textView.textStorage.length)options:NSStringEnumerationByWords usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
if (NSLocationInRange(characterIndex, enclosingRange)) {
// Do your thing with the word, at range 'enclosingRange'
//[mutableAttributedString setTextColor:[UIColor redColor] range:NSMakeRange(range.location, [word1 length])];
// Change color of text.
//[attributedStringText addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:255.0/255.0 green:0.0/255.0 blue:0.0/255.0 alpha:1.0] range:NSMakeRange(enclosingRange.location,[word1 length])];
NSLog(#"%#",textView.tintColor);
NSLog(#"%#",);
[attributedStringText addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(enclosingRange.location,[word1 length])];
[textView setAttributedText:attributedStringText];
*stop = YES;
}
}];
}
}

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