I want to create a text view which will support highlighting of basic things, like links and hashtags. The similar features can be found in Twitter.app:
It is not necessary to support clicking on those links, just need to highlight all things properly while user is editing contents of text view.
The question is, what is the best way to do that? I don't really want to use heavy-weight syntax highlighting libraries, but I didn't find any simple and small libraries to highlight only a few things.
Should I parse text and highlight it by myself? If I should, what libraries can I use to tokenise text, and what libraries will allow me to make live highlighting?
Yes, if you want that light-weight use your own parsing to find relevant parts and then use the textStorage of NSTextView to change text attributes for the found range.
Have you tried using Regular Expressions to match your text (in background, when text is updated)? After you find matches, it is pretty simple to set required attributes (of a NSAttributedString).
You can have a look at Regular expressions in an Objective-C Cocoa application
Here is a small example:
you need to implement this delegate method, textview1 is outlet of TextView:
- (BOOL)textView:(NSTextView *)textView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString
{
NSLog(#"string is this");
NSString *allTheText =[textView1 string];
NSArray *lines = [allTheText componentsSeparatedByString:#""];
NSString *str=[[NSString alloc]init];
NSMutableAttributedString *attr;
BOOL isNext=YES;
[textView1 setString:#""];
for (str in lines)
{
attr=[[NSMutableAttributedString alloc]initWithString:str];
if ([str length] > 0)
{
NSRange range=NSMakeRange(0, [str length]);
[attr addAttribute:NSLinkAttributeName value:[NSColor greenColor] range:range];
[textView1 .textStorage appendAttributedString:attr];
isNext=YES;
}
else
{
NSString *str=#"";
NSAttributedString *attr=[[NSAttributedString alloc]initWithString:str];
[textView1 .textStorage appendAttributedString:attr];
isNext=NO;
}
}
}
this will give you text in blue color with hyperlink;.
Related
I'm using the NSAttributedString UIKit Additions to draw an attributed string in a UIView. The problem I have is that despite using a value of NSWritingDirectionNatural for the baseWritingDirection property of my paragraph style, text always defaults to left-to-right.
Here's how I form the attributed string (simplified):
NSString *arabic = #"العاصمة الليبية لتأمينها تنفيذا لقرار المؤتمر الوطني العام. يأتي ذلك بعدما أعلن اللواء الليبي المتقاعد خليفة حفتر أنه طلب من المجلس الأعلى للقض الدولة حتى الانتخابات النيابية القادمة";
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.baseWritingDirection = NSWritingDirectionNatural;
paragraph.lineBreakMode = NSLineBreakByWordWrapping;
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
attributes[NSParagraphStyleAttributeName] = paragraph;
NSAttributedString *string = [[NSAttributedString alloc]
initWithString:arabic
attributes:attributes];
And here's how I draw the text:
- (void)drawRect:(CGRect)rect {
[self.attributedText drawWithRect:rect
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
}
And yet it still flows from left to right:
What am I missing?
I don't believe the writing direction will be automatically set for you using baseWritingDirection unless you switch languages on the device:
"If you specify NSWritingDirectionNaturalDirection, the receiver resolves the writing direction to either NSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, depending on the direction for the user’s language preference setting."
For some reason the text you have still doesn't seem to work even with arabic selected without adding the language to your supported localizations. This character seemed to work without doing that for me: كتب
Also, it looks like Xcode reverses the characters in hardcoded arabic strings so that may be screwing with some of this copy and paste.
You can use agiletortoises's suggestion or NSLinguisticTagger's Language tag scheme to manually set the language.
I can't explain why it does not work the way you have it written, but I've been using a solution to explicitly set the direction based on known RTL languages, which used this as a starting point:
https://stackoverflow.com/a/16309559
No idea if this is related or not, but when I tested the right-to-left support in Auto Layout, it didn't work until I added a localization for that language (Arabic, Hebrew, etc) to the app.
I'm working on adding some syntax highlighting to an app. In a testing class, I currently have an NSTextView with the textDidChange notification. Similar to this:
-(void)textDidChange:(NSNotification *)notification
{
[self highlightText];
}
What highlight text does, it grab the string from the NSTextView parse it and create a NSMutableAttributedString and finally displays the string. The code is something similar to this: (I use ParseKit to do my parsing. The below sample just highlights code comments).
- (void) highlightText
{
NSMutableAttributedString * resultString = [[NSMutableAttributedString alloc] initWithString: inputTextView.string];
PKTokenizer *t = [PKTokenizer tokenizerWithString: inputTextView.string];
[t setTokenizerState: t.quoteState from: '[' to: ']'];
// We want comments
t.commentState.reportsCommentTokens = YES;
[t enumerateTokensUsingBlock: ^(PKToken * token, BOOL * stop)
{
// Comments take presidense.
if(token.isComment)
{
[resultString addAttribute: NSForegroundColorAttributeName
value: [self commentColor]
range: NSMakeRange(token.offset, token.stringValue.length)];
}
}];
// Monospace
[resultString addAttribute: NSFontAttributeName
value: [NSFont userFixedPitchFontOfSize:0.0]
range: NSMakeRange(0, inputTextView.string.length)];
[[inputTextView textStorage] setAttributedString: resultString];
}
Now this works fine if I am working with a small amount of text, but I would like to improve its performance when working with larger amounts of text. I had two thoughts on the subject:
Do the processing in the background. As the user types, this means text could be unformatted for a few seconds. I don't really like this idea.
Perform highlighting only on the visible section of text. Do more highlighting as the user scrolls. This still has the problem that as the user scrolls, the text would be unformatted but slowly pop into a formatted style.
Does anyone have any suggestions around this area? Am I missing an alternative way to do this, or should this work fine? Does anyone possibly know of any sample code doing something similar/better? I'm currently thinking of going for option #2.
I found a few resources that helped me out:
http://cocoadev.com/ImplementSyntaxHighlighting
What is the best way to implement syntax highlighting of source code in Cocoa?
NSTextView syntax highlighting
(Updated: broken cocoadev link)
I have a NSScrollView item in my main window. Whenever I launch the program, there is text in the scroll view and it starts at the center. Shouldn't the user start reading at the top but why does the app launch it at the center? Thanks!
This can happen if you do not set your views up correctly.
Here's the information I have:
1: You use Interface Builder to set up your views.
2: You are using a NSTextView
3: The code you use for changing the text is:
[[_chapterContent insertText:[Book objectForKey:bookAndChapters]];
4: As you're using a NSTextView, we're not dealing with an issue caused by flipped coordinates.
I'm not sure whether you should use insertText for this task.
At least, you can use something like this...
range.location = 0;
range.length = 0;
[textView setSelectedRange:range];
...to set the position of the caret.
You can then do a...
[textView scrollRangeToVisible:range];
...if you like.
Note: You can use setSelectedRange with a length of 0 and any location within [textStorage length], to position the caret.
-Let me know if this makes the change you need.
I'd suggest using...
textStorage = [textView textStorage];
range.location = 0;
range.length = [textStorage length];
if([self shouldChangeTextInRange:range replacementString:string])
{
[textStorage beginEditing];
[textStorage replaceCharactersInRange:range withAttributedString:attrStr];
[textStorage endEditing];
}
... to replace the text with an attributed string.
If you don't need an attributed string, there's a simpler method available:
[textStorage replaceCharactersInRange:range withString:str];
It's worked well for me (and a number of other people) for many years; it's the recommended way of changing the contents of the NSTextView.
-If you just want to replace the entire contents with a string, you can use
[textView setString:string];
When I paste text to a NSTextView, I wish I can paste plain text only. All the rich text formats should be removed, include: font, color, link, and paragraph style. All the text pasted should be displayed with the default font and style of the text view. NSTextView accepts rich text by default, how to disable it?
Use isRichText = false to disable rich text.
Override this method in your NSTextView:
- (NSString *)preferredPasteboardTypeFromArray:(NSArray *)availableTypes restrictedToTypesFromArray:(NSArray *)allowedTypes {
if ([availableTypes containsObject:NSPasteboardTypeString]) {
return NSPasteboardTypeString;
}
return [super preferredPasteboardTypeFromArray:availableTypes restrictedToTypesFromArray:allowedTypes];
}
For me this worked both for pasting and drag-and-drop.
Define a custom NSTextView class with following method:
- (NSArray *)readablePasteboardTypes {
return [NSArray arrayWithObjects:NSStringPboardType,
nil];
}
Note: As of Mac OS X the new typedef for the pasteboard type is given as NSPasteboardTypeString instead of NSStringPBoardType:
- (NSArray *)readablePasteboardTypes {
return [NSArray arrayWithObjects:NSPasteboardTypeString,
nil];
}
The above solutions do resolve the questioner's issue of pasted-in text, but I think that the questioner probably wanted more than that. At least I did when I came here.
I simply want all the characters in my text field to always have the same font, regardless of whether they are inserted programatically, from the nib, pasted in, dragged in, typed in, or dropped in by Santa Claus. I searched Stack Overflow for this broader issue but did not find any questions (or answers).
Instead of the solutions given here, use this idea. In detail, give the text field a delegate which implements this…
- (void)textViewDidChangeSelection:(NSNotification *)note {
NSTextView* textView = [note object] ;
[textView setFont:[self fontIWant]] ;
}
Done. This works for all of the edge cases I could think of to test. It's a little weird, to observe a change in the selection for this. Seems like observing the string value of the view's text object, or registering for NSTextDidChangeNotification, would be more logical, but since I already had a delegate set up, and since the above was tested and given the thumbs-up by Nick Zitzmann, I went with it.
I'm trying to write a "highlight" feature on an NSTextView. Currently, everything works great. You select a range of text and the background color of that text changes to yellow. However, while it's still selected, the background is that standard blue of selected text. How do I make that standard selection indicator color not show up in certain cases?
Thanks!
Use -[NSTextView setSelectedTextAttributes:...].
For example:
[textView setSelectedTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSColor blackColor], NSBackgroundColorAttributeName,
[NSColor whiteColor], NSForegroundColorAttributeName,
nil]];
You can simply pass an empty dictionary if you don't want the selection indicated in any way at all (short of hiding the insertion point).
Another option is to watch for selection changes and apply the "selection" using temporary attributes. Note that temporary attributes are used to show spelling and grammar mistakes and find results; so if you care about preserving these features of NSTextView then make sure only to add and remove temporary attributes, not replace them.
An example of this is (in a NSTextView subclass):
- (void)setSelectedRanges:(NSArray *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag;
{
NSArray *oldRanges = [self selectedRanges];
for (NSValue *v in oldRanges) {
NSRange oldRange = [v rangeValue];
if (oldRange.length > 0)
[[self layoutManager] removeTemporaryAttribute:NSBackgroundColorAttributeName forCharacterRange:oldRange];
}
for (NSValue *v in ranges) {
NSRange range = [v rangeValue];
if (range.length > 0)
[[self layoutManager] addTemporaryAttributes:[NSDictionary dictionaryWithObject:[NSColor blueColor] forKey:NSBackgroundColorAttributeName]
forCharacterRange:range];
}
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
}