I would like to display a two-line NSAttributedString as the button title of the NSStatusItem of my macOS app.
However, it seems to move the text up a few pixels and, thus, cut it off. This problem did not occur before macOS Big Sur.
Workaround
With some effort I managed to generate an NSImage of the text and use it as the button's image.
Question
Is there any way to position the NSAttributedString correctly without using an image?
I found a way to workaround this problem, but I don’t know if this way is correct, the code with Objetive-C is as follows
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
CGFloat minMaxLineHeight = (font.pointSize - font.ascender + font.capHeight);
[style setMinimumLineHeight:minMaxLineHeight];
[style setMaximumLineHeight:minMaxLineHeight];
NSRange range = NSMakeRange(0, text.length);
[attriString addAttribute:NSParagraphStyleAttributeName
value:style
range:range];
[attriString addAttribute:NSBaselineOffsetAttributeName
value:#(-3.5)
range:range];
Related
I've stumbled upon a behavior in NSTextView that does not seem intended, or that I at least do not understand the reasoning behind.
When you have a large body of text in an NSTextView and you resize the control/window, the wrapping of words only happens fluently and immediately while resizing when the text is scrolled near the top. If you scroll far down in the text, it does not, and it doesn't seem to "commit" the wrapping until you release and finish resizing.
Is there some internal limitation, or is this a bug?
The issue seems to be reproducible:
macOS 10.15.4, Xcode 11.4.1
Create a new macOS App project
Put an NSTextView on the default generated view controller (doesn't matter which of the 3: rich, plain or default) and constrain it so that it resizes with the window (top, bottom, leading, trailing)
Run the application and paste a large body of text into the text view (for example: http://www.gutenberg.org/cache/epub/12281/pg12281.txt)
Scroll to the top of the NSTextView and observe how the text wraps while resizing the window
Scroll to the bottom and observe how it only wraps after resizing the window
Hoping there's any Cocoa detectives out there who can provide some enlightenment on this one.
EDIT:
As per the docs, it states that "the layout manager reserves the right to perform layout for larger ranges". I take it that this means it is indeed intended as a performance consideration.
Is there any way to determine what the limit is, though?
EDIT: You could try subclassing NSScrollView to render the text into multiple containers.
NSTextStorage *storage = [[NSTextStorage alloc] initWithString:string];
NSLayoutManager *manager = [[NSLayoutManager alloc] init];
[storage addLayoutManager:manager];
NSInteger i = 0;
while (YES) {
NSTextContainer *container = [[NSTextContainer alloc] initWithSize:CGSizeMake(width, height)];
[manager addTextContainer:container];
NSTextView *textView = [[NSTextView alloc] initWithFrame:CGRectMake(x, y, width, height) textContainer:container];
[self.contentView addSubview:textView];
i++;
NSRange range = [manager glyphRangeForTextContainer:container];
if ( range.length + range.location == string.length )
break;
}
Then, while resizing the window, you can call NSLayoutManager to ensure the layout only for visible containers.
I want to display tooltips over parts of an attributed string, and thought that using an NSToolTipAttributeName attribute on the required ranges would do the trick but I can't get it to work.
I am trying in MACOS, Xcode (9.2)
NSString *fullString = #"Create a new tooltip";
NSString *compareString =#"tooltip";
NSRange tooltipRange = [fullString rangeOfString: compareString];
[senderTextView.textStorage addAttribute:NSToolTipAttributeName
value:#"Custom tooltip"
range:tooltipRange];
[senderTextView.textStorage addAttribute:NSForegroundColorAttributeName
value:[NSColor redColor] range:tooltipRange];
The word "tooltip" is shown in red as expected, but no tooltip appears when I hover over it. What am I missing?
Couple of ways to do this, using the NSToolTipAttributeName in combination with NSLinkAttributeName, e.g.:
NSMutableDictionary* labelAttributes = [NSMutableDictionary new];
labelAttributes[NSToolTipAttributeName] = #"Tooltip";
labelAttributes[NSLinkAttributeName] = #"Link";
NSAttributedString* labelString = [NSAttributedString
attributedString:#"Display"]
withAttributes:labelAttributes];
Being careful not to forget displaysLinkToolTips = YES on the NSTextView.
Alternately, you can use the NSView Tooltips API, which provides:
- (NSToolTipTag)addToolTipRect:(NSRect)rect owner:(id)owner userData:(void *)data;
It seems that NSButtonCell's setFont method is not available anymore from 10.9.
Is there any way (or category) to (re)implement it?
I don't know why Apple forces it's own styles on buttons.
I am trying for 2 days to style my own custom button (I also needed a category to simply change the button's text color - shame).
You can use -setAttributedTitle: of NSButton to set the style of button title.
Sample code:
NSDictionary *attributes = #{NSForegroundColorAttributeName: [NSColor redColor], NSFontAttributeName: [NSFont userFixedPitchFontOfSize:13.0f]};
NSAttributedString *aString = [[NSAttributedString alloc] initWithString:#"Button" attributes:attributes];
[button setAttributedTitle:aString];
I would like to display in an NSView a single-paged PDF.
So far, I have two solutions but they both have downsides. Can anyone help me with any of these downsides?
First solution: with NSImage and NSImageView
NSString *path= [[NSBundle mainBundle] pathForResource:name ofType:#"pdf"];
NSImage * image = [[NSImage alloc] initWithContentsOfFile:path] ;
NSImageView * imageView = [[NSImageView alloc] init] ;
imageView.frame = NSMakeRect(0, 0, 2*image.size.width, 2*image.size.height) ;
imageView.image = image ;
imageView.imageScaling = NSImageScaleAxesIndependently ;
return imageView
Downsides:
the image is not anti-aliased
I don't understand why the factor 2 is needed. Why does my PDF is displayed smaller in an NSView than it is with the Finder?
Second solution: with PDFDocument and PDFView
NSString *path= [[NSBundle mainBundle] pathForResource:name ofType:#"pdf"];
NSURL *urlPDF = [NSURL fileURLWithPath:path] ;
PDFDocument * myPDFDocument = [[PDFDocument alloc] initWithURL:urlPDF] ;
PDFView *myPDFView = [[PDFView alloc] init] ;
myPDFView.document = myPDFDocument ;
PDFPage * firstPage = [myPDFDocument pageAtIndex:0] ;
NSRect myBounds = [firstPage boundsForBox:kPDFDisplayBoxMediaBox] ;
NSRect myNewBounds = NSMakeRect(0, 0, myBounds.size.width*2, myBounds.size.height*2+5) ;
myPDFView.frame = myNewBounds ;
myPDFView.autoScales = YES ;
return myPDFView ;
Downsides:
I am able to select the text of my pdf, I can zoom in or zoom out. But I would like my PDF document to be displayed as an image, without these possibilities
I don't understand why the factor 2 is needed. Why is my PDF displayed smaller in an NSView than it is with the Finder?
There are some margins around my image
I'm not seeing the problems you describe with NSImageView. I implemented a nib-based window and NSImageView. In my case I have an overlapping sibling view, so I turned CALayers turned on in the nib. I'm on 10.9.2. Sizing is normal (1x) and the text in my PDF is anti-aliased (sub-pixel I think, since I see colors when I blow it up). I do have scaling NONE - maybe scaling is preventing anti-aliased text?
Otherwise my guess is there's something different about your views or or PDF content. Try a simpler PDF and/or a nib-based view and if it works, you can look for differences.
I have a basic NSRecessedBezelStyle NSButton added via IB to an NSView. Why is the font messed up in its unselected state? Is this normal?
As you can see, when pushed the recessed button looks fine, but unpressed it's solid black with no shadow. Am I missing something really obvious somewhere? I tried messing around with setAttributedTitle and setAttributedAlternateTitle but that yielded odd results with the push on push off mechanic.
That is the expected behavior for NSRecessedBezelStyle with the default "Push On Push Off" Type, bezeled in On state, plain text in OFF, additionally you can change the Type so the bezel is only displayed when hovering, here is the code to make it gray.
NSMutableDictionary *attrsDictionary = [NSMutableDictionary dictionaryWithCapacity:1];
[attrsDictionary setObject:[NSColor grayColor] forKey:NSForegroundColorAttributeName];
[attrsDictionary setObject:[NSFont boldSystemFontOfSize:12.0] forKey:NSFontAttributeName];
NSMutableParagraphStyle *paragraph = [[[NSMutableParagraphStyle alloc] init] autorelease];
[paragraph setAlignment:NSCenterTextAlignment];
[attrsDictionary setObject:paragraph forKey:NSParagraphStyleAttributeName];
NSAttributedString *str = [[[NSAttributedString alloc] initWithString:#"Button" attributes:attrsDictionary] autorelease];
[button setAttributedTitle:str];