Is there any way to get the NSParagraphStyle of an UILabel instead of creating a new instance and settings every attribute?
You can use enumerateAttribute:inRange:options:usingBlock: to retrieve the NSParagraphStyle on the attributedText property of your UILabel object:
NSAttributedString *attributedString = myLabel.attributedText;
[attributedString enumerateAttribute:NSParagraphStyleAttributeName
inRange:NSMakeRange(0, attributedString.length)
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
NSParagraphStyle *paragraphStyle = value; // Do what you want with paragraph
}];
The code is not tested (may not compile due to some small mistakes), but it should give you the idea behind it.
Related
I'm trying to use an NSLinguisticTagger to monitor the contents of an NSTextStorage and provide some contextual information based on what the user types. To that end, I have an OverlayManager object, which wires up this relationship:
-(void) setView:(NSTextView*) view {
_view = view;
_layout = view.layoutManager;
_storage = view.layoutManager.textStorage; //get the TextStorage from the view
[_tagger setString:_storage.string]; //pull the string out, this grabs the mutable version
[self registerForNotificationsOn:self->_storage]; //subscribe to the willProcessEditing notification
}
When an edit occurs, I make sure to trap it and notify the tagger (and yes, I know I'm being annoyingly inconsistent with member access, I'm rusty on Obj-C, I'll fix it later):
- (void) textStorageWillProcessEditing:(NSNotification*) notification{
if ([self->_storage editedMask] & NSTextStorageEditedCharacters) {
NSRange editedRange = [self->_storage editedRange];
NSUInteger delta = [self->_storage changeInLength];
[_tagger stringEditedInRange:editedRange changeInLength:delta]; //should notify the tagger of the changes- if I replace this line with [_tagger setString:[_storage string]] it works, but gobbles CPU as it retags the entire string
[self highlightEdits:self];
}
}
The highlightEdits message delegates the job out to a pool of "Overlay" objects. Each contains a block of code similar to this:
[tagger enumerateTagsInRange:range scheme:NSLinguisticTagSchemeLexicalClass options:0 usingBlock:^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
if (tag == PartOfSpeech) {
[self applyHighlightToRange:tokenRange onStorage:storage];
}
}];
And that's where the problem is- the enumerateTagsInRange method crashes out with a message:
2014-06-04 10:07:19.692 WritersEditor[40191:303] NSMutableRLEArray replaceObjectsInRange:withObject:length:: Out of bounds
This problem doesn't occur if I don't link to the mutable copy of the underlying string and instead do a [[_storage string] copy], but obviously I don't want to copy the entire backing store every time I want to do tagging. This all should be happening in the main run loop, so I don't think this is a threading issue. The NSRange I'm enumerating tags on exists both in the NSTextStorage and in the NSLinguisticTagger's view of the string. It's not even the fact that the applyHighlightToRange call adds attributes to the string, because it crashes before even reaching that line.
I attempted to build a test case around the problem, but can't replicate it in those situations:
- (void) testEdit
{
NSAttributedString* str = [[NSMutableAttributedString alloc] initWithString:#"Quickly, this is a test."];
text = [[NSTextStorage alloc] initWithAttributedString:str];
NSArray* schemes = [NSLinguisticTagger availableTagSchemesForLanguage:#"en"];
tagger = [[NSLinguisticTagger alloc] initWithTagSchemes:schemes options:0];
[tagger setString:[text string]];
[text beginEditing];
[[text mutableString] appendString:#"T"];
NSRange edited = [text editedRange];
NSUInteger length = [text changeInLength];
[text endEditing];
[tagger stringEditedInRange:edited changeInLength:length];
[tagger enumerateTagsInRange:edited scheme:NSLinguisticTagSchemeLexicalClass options:0 usingBlock:^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
//doesn't matter, this should crash
}];
}
That code doesn't crash.
Right now I'm using
NSShadow *textShadow = [NSShadow new];
textShadow.shadowBlurRadius = 5;
textShadow.shadowColor = [[NSColor whiteColor] colorWithAlphaComponent:.5];
[self addAttribute:NSShadowAttributeName value:textShadow range:NSMakeRange(0, self.length)];
from an NSTextStorage to give text a shadow. But I want to apply more than one shadow, and adding another NSShadowAttributeName just overwrites the previous value.
How can I add more than one shadow? Can it be done with CGContextSetShadowWithColor?
Not sure please try this below code for your textview. When you write string inside textview it will select that much range and on the basis of that it draws the color:-
-(IBAction)createNewTabView:(id)sender
{
NSString *allTheText =[tv string];
NSArray *lines = [allTheText componentsSeparatedByString:#"\n"];
NSString *str=[[NSString alloc]init];
NSMutableAttributedString *attr;
BOOL isNext=YES;
[tv setString:#""];
for (str in lines)
{
attr=[[NSMutableAttributedString alloc]initWithString:str];
if ([str length] > 0)
{
NSRange range=NSMakeRange(0, [str length]);
[attr addAttribute:NSBackgroundColorAttributeName value:[NSColor greenColor] range:range];
[tv .textStorage appendAttributedString:attr];
isNext=YES;
}
else
{
NSString *str=#"\n";
NSAttributedString *attr=[[NSAttributedString alloc]initWithString:str];
[tv .textStorage appendAttributedString:attr];
isNext=NO;
}
if (isNext==YES)
{
NSString *str=#"\n";
NSAttributedString *attr=[[NSAttributedString alloc]initWithString:str];
[tv .textStorage appendAttributedString:attr];
}
}
}
I would suggest the "caveman" approach. Instead of trying to get duplicate shadows, use duplicate Text's, all but one of which has it's color set to the clear color, and give them different dropShadows.
make two ore more text's that line up exactly.
make the front most your "real" text color.
make the other text's clear color.
set one type of shadow for each type of text.
You should be able to make a class that automates this, if you are going to use it multiple times.
This is a sample of what I got:
oh-- if you don't make the additional text's your text will appear much darker/bolder than you expect.
I can’t figure out how to set the font/styling of my NSMenuItems in my NSMenu. I tried the setFont method on the NSMenu but it doesn’t seem to have any effect on the menu items. NSMenuItem doesn’t seem to have a setFont method. I would like for them all to have the same font/style so I would hope there’s just one property I can set somewhere.
They can have an attributed title, so you can set an attributed string as title with all it's attributed, font included:
NSMutableAttributedString* str =[[NSMutableAttributedString alloc]initWithString: #"Title"];
[str setAttributes: #{ NSFontAttributeName : [NSFont fontWithName: #"myFont" size: 12.0] } range: NSMakeRange(0, [str length])];
[label setAttributedString: str];
NSMenuItem has support for attributed strings as titles:
- (void)setAttributedTitle:(NSAttributedString *)string;
Example code:
NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:#"Hi, how are you?" action:nil keyEquivalent:#""];
NSDictionary *attributes = #{
NSFontAttributeName: [NSFont fontWithName:#"Comic Sans MS" size:19.0],
NSForegroundColorAttributeName: [NSColor greenColor]
};
NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:[menuItem title] attributes:attributes];
[menuItem setAttributedTitle:attributedTitle];
Documentation: https://developer.apple.com/library/mac/#documentation/cocoa/reference/applicationkit/classes/nsmenuitem_class/reference/reference.html
+ menuBarFontOfSize: from NSFont is your friend here.
If you don't plan to change the font family, you should use [NSFont menuBarFontOfSize:12] to get the default font and set a new size.
If you are only changing the color, you still need to set the default font size back by doing [NSFont menuBarFontOfSize:0].
So to only change the NSMenuItem color:
NSDictionary *attributes = #{
NSFontAttributeName: [NSFont menuBarFontOfSize:0],
NSForegroundColorAttributeName: [NSColor greenColor]
};
NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:[menuItem title] attributes:attributes];
[menuItem setAttributedTitle:attributedTitle];
Actually [NSMenu setFont:] works for all menu items submenus (if last ones doesn't have their own font). Maybe you set attributed title before setting the menu font?
Realized it, after writing own procedure to iterate through menu items.
In case you need some custom processing (i.e. change font for not all items, or customize it for different items) here is a simple iterating code:
#implementation NSMenu (MenuAdditions)
- (void) changeMenuFont:(NSFont*)aFont
{
for (NSMenuItem* anItem in self.itemArray)
{
NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:aFont forKey:NSFontAttributeName];
anItem.attributedTitle = [[[NSAttributedString alloc] initWithString:anItem.title attributes:attrsDictionary] autorelease];
if (anItem.submenu)
[anItem.submenu changeMenuFont:aFont];
}
}
#end
I'm actually starting to loose the will to live, this piece of code is driving me nuts!
I'm trying to get the content of mathspractice.txt into *myLabel
I'm using an array which is:
-(void)loadText
{
NSArray *wordListArray = [[NSArray alloc] initWithArray:
[[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#”mathspractice” ofType:#”txt”]
encoding:NSMacOSRomanStringEncoding error:NULL] componentsSeparatedByString:#”\n”]];
self.theMathsPractice = wordListArray;
[wordListArray release];
}
and then I'm trying to pass it into *myLabel
UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,100,960,40)];
myLabel.text = *theMathsPractice;
[myScrollView addSubview:myLabel];
[myLabel release];
}
Can anyone help?
It looks on quick inspection that your theMathsPractice is an NSArray, not an NSString, which is what you'd want to assign to the label's text property. You should at least format that array back into a string of some sort before assigning it to the label.
(Also not sure why you're dereferencing it with the * in the assignment-- I would think that would throw a compiler error, since naked non-reference Objective-C objects are not really allowed.)
I would use the following:
myLable.text = [theMathsPractice componentsJoinedByString:#" "]);
I need to set a text size (for example to 42) of the selected rich text which uses multiple fonts.
I imagine I can check attributes of each group of characters, modify the font size and set attributes back, but looking at the floating Font panel it seems like there should be a very easy and straightforward way to accomplish that. Do I miss something obvious?
On 10.6 there is a convenient way to iterate over the attributes and increase the font size.
This method can be added to an NSTextView category.
- (IBAction)increaseFontSize:(id)sender
{
NSTextStorage *textStorage = [self textStorage];
[textStorage beginEditing];
[textStorage enumerateAttributesInRange: NSMakeRange(0, [textStorage length])
options: 0
usingBlock: ^(NSDictionary *attributesDictionary,
NSRange range,
BOOL *stop)
{
#pragma unused(stop)
NSFont *font = [attributesDictionary objectForKey:NSFontAttributeName];
if (font) {
[textStorage removeAttribute:NSFontAttributeName range:range];
font = [[NSFontManager sharedFontManager] convertFont:font toSize:[font pointSize] + 1];
[textStorage addAttribute:NSFontAttributeName value:font range:range];
}
}];
[textStorage endEditing];
[self didChangeText];
}
Generalizing on Jonathan's answer a bit, here is a category interface you can simply paste into appropriate files in your Xcode project:
#interface NSTextView (FrameworkAdditions)
- (IBAction)decrementFontSize:(id)sender;
- (IBAction)incrementFontSize:(id)sender;
#end
And the corresponding implementation:
#implementation NSTextView (FrameworkAdditions)
- (void)changeFontSize:(CGFloat)delta;
{
NSFontManager * fontManager = [NSFontManager sharedFontManager];
NSTextStorage * textStorage = [self textStorage];
[textStorage beginEditing];
[textStorage enumerateAttribute:NSFontAttributeName
inRange:NSMakeRange(0, [textStorage length])
options:0
usingBlock:^(id value,
NSRange range,
BOOL * stop)
{
NSFont * font = value;
font = [fontManager convertFont:font
toSize:[font pointSize] + delta];
if (font != nil) {
[textStorage removeAttribute:NSFontAttributeName
range:range];
[textStorage addAttribute:NSFontAttributeName
value:font
range:range];
}
}];
[textStorage endEditing];
[self didChangeText];
}
- (IBAction)decrementFontSize:(id)sender;
{
[self changeFontSize:-1.0];
}
- (IBAction)incrementFontSize:(id)sender;
{
[self changeFontSize:1.0];
}
#end
This will double the font size, but you may change the scale property to any value, or provide your fixed size
NSFont * font = ...;
CGFloat fontSize = [[font fontDescriptor].fontAttributes[NSFontSizeAttribute] floatValue];
font = [NSFont fontWithDescriptor:[font fontDescriptor] size:fontSize * 2.];
self.textField.font = font;
Note: I assume that you are using a NSTextView and that you can access its text storage (NSTextStorage).
I think it is not possible to only change the font's size over a text that use multiple fonts. In NSAttributedString, font's size is part of the NSFontAttributeName attribute which controls both the font and the size.
One solution is to iterate over the selection and use the attribute:atIndex:longestEffectiveRange:inRange: to capture the range when each font apply, change the font's size and then use the addAttribute:value:range: to set the new font over the range.
Update:
If you take a look at the GNUstep GUI source code for NSTextView (under LGPL), you will see that their implementation use the range iteration.
Since NSTextView is a subclass of NSView, you can use -scaleUnitSquareToSize: to change the magnification level of the text view. For example, to make all the text double sized you'd call:
[textView scaleUnitSquareToSize:NSMakeSize(2.0, 2.0)];
You may need to make some adjustments to the dimensions of the text view's NSTextContainer after performing this operation to ensure the text is laid out correctly.