NSMutableParagraphStyle ignores NSWritingDirectionNatural, is defaulting to LTR for arabic text - uikit

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.

Related

NSTextView live text highlighting

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;.

Improving performance on NSTextView syntax highlighting via NSAttributedString

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)

When I paste text to a NSTextView, how to paste plain text only?

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.

UIKit's [NSString sizeWithFont:constrainedToSize:] in AppKit

Is there any equivalent method in AppKit (for Cocoa on Mac OS X) that does the same thing as UIKit's [NSString sizeWithFont:constrainedToSize:]?
If not, how could I go about getting the amount of space needed to render a particular string constrained to a width/height?
Update: Below is a snippet of code I'm using that I expect would produce the results I'm after.
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize: [NSFont smallSystemFontSize]], NSFontAttributeName,
[NSParagraphStyle defaultParagraphStyle], NSParagraphStyleAttributeName,
nil];
NSSize size = NSMakeSize(200.0, MAXFLOAT);
NSRect bounds;
bounds = [#"This is a really really really really really really really long string that won't fit on one line"
boundingRectWithSize: size
options: NSStringDrawingUsesFontLeading
attributes: attributes];
NSLog(#"height: %02f, width: %02f", bounds.size.height, bounds.size.width);
I would expect that the output width would be 200 and the height would be something greater than the height of a single line, however it produces:
height: 14.000000, width: 466.619141
Thanks!
Try this one:
bounds = [value boundingRectWithSize:size options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin attributes:attributes];
The newer NSExtendedStringDrawing category API (methods with the NSStringDrawingOptions argument) behaves in the single line mode. If you want to measure/render in multi line mode, want to specify NSStringDrawingUsesLineFragmentOrigin.
EDIT: You should be able to do things the normal way in Lion and later. The problems described below have been fixed.
There is no way to accurately measure text among the current Mac OS X APIs.
There are several APIs that promise to work but don't. That's one of them; Core Text's function for this purpose is another. Some methods return results that are close but wrong; some return results that seem to mean nothing at all. I haven't filed any bugs on these yet, but when I do, I'll edit the Radar numbers into this answer. You should file a bug as well, and include your code in a small sample project.
[Apparently I have already filed the Core Text one: 8666756. It's closed as a duplicate of an unspecified other bug. For Cocoa, I filed 9022238 about the method Dave suggested, and will file two NSLayoutManager bugs tomorrow.]
This is the closest solution I've found.
If you want to constrain the string to a certain size, you use -[NSString boundingRectWithSize:options:attributes:]. The .size of the returned NSRect is the size you're looking for.
Here is a more complete example of how you can do this using boundingRectWithSize.
// get the text field
NSTextField* field = (NSTextField*)view;
// create the new font for the text field
NSFont* newFont = [NSFont fontWithName:#"Trebuchet MS" size:11.0];
// set the font on the text field
[field setFont:newFont];
// calculate the size the textfield needs to be in order to display the text properly
NSString* text = field.stringValue;
NSInteger maxWidth = field.frame.size.width;
NSInteger maxHeight = 20000;
CGSize constraint = CGSizeMake(maxWidth, maxHeight);
NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys:NSFontAttributeName,newFont, nil];
NSRect newBounds = [text boundingRectWithSize:constraint
options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin
attributes:attrs];
// set the size of the text field to the calculated size
field.frame = NSMakeRect(field.frame.origin.x, field.frame.origin.y, field.frame.size.width, newBounds.size.height);
Of course, for additional info, take a look at the Apple documentation:
Options for the attributes dictionary
boundingRectWithSize
If you search the documentation for NSString, you will find the "NSString Application Kit Additions Reference", which is pretty much analogous to the UIKit counterpart.
-[NSString sizeWithAttributes:]
is the method you are looking for.

Dynamic Results and Covering Data

Today I have a question that sprouted off of this one: Database Results in Cocoa. It's regarding using the data that was returned by a database to create a certain number of questions. I am using a form of the following code (this was posted in the question).
NSMutableDictionary * interfaceElements = [[NSMutableDictionary alloc] init];
for (NSInteger i = 0; i < numberOfTextFields; ++i) {
//this is just to make a frame that's indented 10px
//and has 10px between it and the previous NSTextField (or window edge)
NSRect frame = NSMakeRect(10, (i*22 + (i+1)*10), 100, 22);
NSTextField * newField = [[NSTextField alloc] initWithFrame:frame];
//configure newField appropriately
[[myWindow contentView] addSubview:newField];
[interfaceElements setObject:newField forKey:#"someUniqueIdentifier"];
[newField release];
}
However, now when I attempt to use IFVerticallyExpandingTextfield (from http://www.cocoadev.com/index.pl?IFVerticallyExpandingTextField), or create any large amount of text, it simply goes over the other drawn content. I looked into using setAutosizingMask: on the object, but it has not worked so far.
Thanks for any help.
EDIT: What I want the effect to look like is called "Correct TextField" and what happens is called "StackOverflow Sample" - http://www.mediafire.com/?sharekey=bd476ea483deded875a4fc82078ae6c8e04e75f6e8ebb871.
EDIT 2: And if no one knows how to use this IFVerticallyExpandingTextfield class, would anyone know if there is another way to accomplish the effect?
Do you mean this?
http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaDrawingGuide/GraphicsContexts/GraphicsContexts.html
Your question is not very clear to me but this might help ^^^.
Look at 'Modifying the Current Graphics State' on that page.
What about just exactly copying the code from the 'Correct textfield' example and use it in your application? Or start your application from the 'Correct texfield' example.
Also
A comment on http://www.cocoadev.com/index.pl?IFVerticallyExpandingTextField says:
Give it a try! You should be able to
throw this into a project, read the
files in Interface Builder, and use
the custom class pane to make an
NSTextField into an
IFVerticallyExpandingTextField. You'll
need to set the layout and
linebreaking attributes for word
wrapping for this to work.
Although expansion should work
properly when the textfield is
embedded in a subview, I'm having some
trouble dealing with NSScrollViews.
The field expands into the
scrollview's content view, but none of
the controls on the scrollbar appear.
Some help here would be appreciated.

Resources