NSTextStorage addAttribute for only specified range of characters - objective-c

I have been working with TextKit and the NSTextStorage object on UITextView to attempt to get certain words to dynamically format.
The following method is in a subclass of UITextView and is executed on the textDidChange event. This works in that it does indeed detect when the word "the" is entered and it does color it red, however all text after the word "the" is also then red. The goal is for only "the" to be red.
Any idea what I am doing wrong?
- (void)highlight {
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\\bthe\\b" options:0 error:nil];
NSArray *matches = [regex matchesInString:[self text] options:0 range:NSMakeRange(0, [self.text length])];
for (NSTextCheckingResult *match in matches) {
[self.textStorage beginEditing];
[self.textStorage addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:match.range];
[self.textStorage endEditing];
}
}

This is happening because NSTextStorage is actually an NSMutableAttributedString subclass, and NSMutableAttributedString works in this way: it extends attributes to inserted text.
Try subclassing NSTextStorage (to so, you need to implement four methods, as mentioned in the NSTextStorage docs). Then, implement -fixAttributesInRange: in your NSTextStorage subclass. You'll need to manage text attributes in this method (such as highlighting the word "the", in the example that you gave) manually.
One trick to improve efficiency is to reprocess as little text as possible. One way to do so is to only process text for the current paragraph that has changed:
NSRange paragaphRange = [self.string paragraphRangeForRange:self.editedRange];
Where self.editedRange will be correctly populated by your superclass to be the range of the text that has just changed.

Related

attributes not saving with file

This is probably easy, but I can not seam to figure it out - maybe it's late. I have a simple program that takes the text from an NSTextView and saves it as rtf. Saving the text itself works great, I just can not figure out how to get the attributes to tag along.
Code:
NSAttributedString *saveString = [[NSAttributedString alloc]
initWithString:[textView string]];
NSData *writeResults = [saveString
RTFFromRange:NSMakeRange:(0, [saveString length])
doumentAttributes:?? ];
[writeResults writeToURL:[panel URL] atomically: YES];
I know I need an NSDictionary for the documentAttributes, so how do I get that from the view?
What am I missing?
It seems that you are asking the textView for its string property. You need to ask it for its attributedString property:
NSAttributedString *saveString = textView.attributedString;
You can get the attributes from an attributed string like this:
NSMutableDictionary *allAttributes = [[NSMutableDictionary alloc] init];
[saveString enumerateAttribuesInRange:NSMakeRange(0,saveString.length) options:NSAttributedStringEnumerationReverse usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
[allAttrubutes addEntriesFromDictionary:attrs];
}];
NSData *writeResults = [saveString.string RTFFromRange:NSMakeRange(0,saveString.length) documentAttributes:allAttributes];
I have used this method to get attributes many times however I have never saved to RTF so I don't know exactly how this will turn out. All the attributes will be in the dictionary however.

iOS 7 using an HTML string as an NSAttributedString AND setting the font?

I have a UITextView in which I'm trying to display some attributed text which comes in the form of HTML. It uses things like inline bolding and italicizing. The client now wants the font on this to change. However, whenever I change the font, it seems to disregard any of the HTML formatting. Is there any way to change the font while retaining the other formatting attributes, like bolding an italicizing?
The way I was able to get it to work was to prepend my string with <span style=\"font-family: Avenir; font-size: 12\">%#</span>
For an objective-c approach, I used NSMutableString and -setAttributes:
NSString *htmlString = #"..."
NSData *textData = [htmlString dataUsingEncoding:NSUnicodeStringEncoding];
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc]
initWithData:textData
options:#{ NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType }
documentAttributes:nil error:nil];
[attrStr setAttributes:#{NSFontAttributeName : [UIFont fontWithName:#"avenir" size:12.0f]} range:NSMakeRange(0,attrStr.length)];

NSMutableAttributedString crashing on changing the font?

I'm sure mutable means it can be changed, so why's this happening?
attrString = [[NSMutableAttributedString alloc] initWithString:#"Tip 1: Aisle Management The most obvious step – although one that still has not been taken by a disconcerting number of organisations – is to configure cabinets in hot and cold aisles. If you haven’t got your racks into cold and hot aisle configurations, we can advise ways in which you can achieve improved airflow performance."];
[attrString setFont:[UIFont systemFontOfSize:20] range:NSMakeRange(0, 23)];
[attrString setFont:[UIFont systemFontOfSize:15] range:NSMakeRange(24, 325)];
[attrString setTextColor:[UIColor blackColor] range:NSMakeRange(0,184)];
[attrString setTextColor:[UIColor blueColor] range:NSMakeRange(185,325)];
break;
Both my catextlayer and my nsmutableattributedsring are defined in my header file. I make the changes to my string above in a switch, then call this code to update the catextlayer the string is shown in:
//updates catext layer
TextLayer = [CATextLayer layer];
TextLayer.bounds = CGRectMake(0.0f, 0.0f, 245.0f, 290.0f);
TextLayer.string = attrString;
TextLayer.position = CGPointMake(162.0, 250.0f);
TextLayer.wrapped = YES;
[self.view.layer addSublayer:TextLayer];
It crashes on when it tries to set the font, but I cant work out why?
-[NSConcreteMutableAttributedString setFont:range:]: unrecognized selector sent to instance 0xd384420
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMutableAttributedString setFont:range:]: unrecognized selector sent to instance 0xd384420'
Why is this happening?
NSMutableAttributedString doesn't have a setFont:range: function.
Taken from here.... iphone/ipad: How exactly use NSAttributedString?
So I did a bit of reading from the docs.
The functions is...
[NSMutableAttirbutedString setAttributes:NSDictionary range:NSRange];
So you should be able to do something like this...
[string setAttributes:#{NSFontAttributeName:[UIFont fontWithName:#"Helvetice-Neue"]} range:NSMakeRange(0, 2)];
or
[string setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont fontWithName:#"Helvetice-Neue"], NSFontAttributeName", nil] range:NSMakeRange(0, 2)];
if you're still using old ObjC syntax.
Hope that helps.
First of all,Is the attrString you said a property?If it's a property,you'd better check that have you declared the property with the copy attribute, and are you presumably using the compiler-generated setter? If YES The compiler-generated setter sends the copy message to the object to make a copy. The copy message makes an immutable copy. That is, it creates an NSAttributedString, not an NSMutableAttributedString.
One way to fix this is to write your own setter that uses mutableCopy, like this if you're using ARC:
- (void)setTextCopy:(NSMutableAttributedString *)text {
textCopy = [text mutableCopy];
}
or like this if you're using manual reference counting:
- (void)setTextCopy:(NSMutableAttributedString *)text {
[textCopy release];
textCopy = [text mutableCopy];
}
Another fix would be to make textCopy be an NSAttributedString instead of an NSMutableAttributedString, and make the rest of your code work with it as an immutable object.
Reference:
1️⃣How to copy a NSMutableAttributedString
2️⃣NSConcreteAttributedString mutableString crash

Why wont [NSTextStorage appendAttributedString: ] accept my NSAttributedString's attributes?

I am trying to put text with a specific font and color into an NSTextView. I can put the text in just fine, but it loses my attributes:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont fontWithName:#"Monaco" size:10], #"NSFontAttributeName",
[NSColor blueColor], #"NSForegroundColorAttributeName",
nil];
NSAttributedString *string = [[NSAttributedString alloc] initWithString:someNSString
attributes: dict];
[someNSTextStorage appendAttributedString:string];
I've read the all the relevant class references (NSTextView, NSTextStorage, NSAttributedString, etc...) and I still don't know why it doesn't work as I expect.
What am I doing wrong/missing?
Found your problem: You've enclosed NSForegroundColorAttributeName in quotes. So instead of a macro you're passing an NSString literal. Same goes for your NSFontAttributeName as well. Get rid of those and you're set.

Cocoa: pasting formatted text in the current font?

When I put formatted text into the pasteboard (NSRTFPboardType), it pastes with all formatting preserved. But what I'd really like is to discard the font face and size information, while preserving the weight, color, etc.
According to the docs, an NSAttributedString with no font information will default to Helvetica 12, so that seems like a dead end.
I can also generate the text on demand, so if I could find out the font in the current UI element, I could modify the text before it goes into the pasteboard. I was hoping the accessibility API could help with this, but none of the attributes I can find in the UIElementInspector seem to deal with formatting.
Any ideas?
Here's a test case. It pastes in Helvetica 12 even though the only attribute is a green color:
// Create the string with no attributes, and strip the font just in case.
NSMutableAttributedString *s = [[[NSMutableAttributedString alloc] initWithString:#"Hello green world!"] autorelease];
[s removeAttribute:NSFontAttributeName range:NSMakeRange(0, [s length])];
// Add a test attribute
[s addAttribute:NSForegroundColorAttributeName value:[NSColor greenColor] range:NSMakeRange(6, 5)];
// Generate RTF data
NSData *rtf = [s RTFFromRange:NSMakeRange(0, [s length]) documentAttributes:nil];
// Copy to pasteboard
NSPasteboard *pb = [NSPasteboard generalPasteboard];
[pb declareTypes:[NSArray arrayWithObject:NSRTFPboardType] owner:nil];
[pb setData:rtf forType:NSRTFPboardType];
Here's something interesting. If I try and generate the plainest raw RTF data I can, with absolutely no font information, it still pastes in Helvetica 12!
char *rawrtf = "{\\rtf1\\ansi\\ansicpg1252\n"
"Hello world.}";
NSData *rtf = [NSData dataWithBytes:rawrtf length:strlen(rawrtf)];
So if this is possible at all, I think it's only possible by querying the currently running application about the current font.
There is no system-wide or even application-wide notion of the "current font". The closest you can get is the typing attributes of a currently active NSTextView; if none is active, then there is nothing like a current font.
With that said, you could promise the RTF data to the pasteboard, and when it requests it, send the currently active application a copy AppleEvent command, wait for a response, pull any rich text off the pasteboard, and grab its font attributes. If no rich text is available, stick with your current font and size. I have no idea how well this heuristic works in practice, but I can't think of any better approach.
If I try and generate the plainest raw RTF data I can, with absolutely no font information, it still pastes in Helvetica 12!
The font defaults to Helvetica-12 when no font information is supplied because all text being drawn has to be in some font at some size. Helvetica-12 must have seemed as readable and sufficiently inoffensive to be chosen as the default. (Sure beats Comic Sans-72!)
NSAttributedString is immutable. I think perhaps what you need is an NSMutableAttributedString. This guide has an example of how to change things. You should be able to create a new NSMutableAttributedString as a copy of the NSAttributedString, and then make your modifications to it before passing it on to the pasteboard.
EDIT:
I'm not quite sure of what you what to do. You are saying that you want to preserve some formatting, and remove other formatting, and it seems like you understand how to manipulate the NSMutableAttributedString attributes, so what's the problem? Create a new attributed string, interrogate the old one, and apply whatever attributes you want to the new one, if you don't like the default font, change it. I'm not understanding where the problem is in this.
EDIT2:
Using initWithAttributedString: should copy the font properties over, but if it doesn't you should be able to use enumerateAttributesInRange:options:usingBlock: to interrogate your original NSAttributedString
EDIT3:
NSAttributedString *attrStr;
NSRange limitRange;
NSRange effectiveRange;
id attributeValue;
limitRange = NSMakeRange(0, [attrStr length]);
while (limitRange.length > 0) {
attributeValue = [attrStr attribute:NSFontAttributeName
atIndex:limitRange.location longestEffectiveRange:&effectiveRange
inRange:limitRange];
// apply attributeValue to the other mutable attributed string range here
limitRange = NSMakeRange(NSMaxRange(effectiveRange),
NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
}
changing copied text so it'll be different from what the user had copied would go against the idea of copy and paste, no?