When using non-standard fonts in Cocoa, they are sometimes misplaced compared to standard system fonts. I've created a sample OS X Cocoa Application which illustrates the issue: http://www.fileconvoy.com/dfl.php?id=g2bff79a0dcf64cc4999493513da42fd8dbb9294e1.
It looks like this:
The label and button on left is the default system font, whereas on the right, the font is changed to Al Bayan (shipped with OS X).
I'm developing an application, where the font has to be changed dynamically. If the user changes the font to one with similar behavior like this, I need some technique to change the margin/padding inside the controls, in this case like:
Button: +5px top margin/padding
Label: -5px top margin/padding
How can this be done?
This is especially worse with "Helvetica Neue" which is kinda standard font (in iOS) and likely to play a bigger role in future OS X versions. I'm not aware of a way to adjust individual controls other than changing the font, but I found a way to render text correctly with own computations. Key is here not to use the string height (e.g. for centering or placing the string), but to take the ascender and xHeight into account.
The following code draws the title of an NSTextFieldCell vertically centered, regardless of the font used:
- (NSRect)titleRectForBounds: (NSRect)theRect
{
NSRect titleFrame = [super titleRectForBounds: theRect];
CGRect rect = [self.attributedStringValue boundingRectWithSize: NSMakeSize(FLT_MAX, NSHeight(titleFrame))
options: 0];
titleFrame.origin.y -= NSHeight(rect) - floor(self.font.ascender);
titleFrame.origin.y += floor((titleFrame.size.height - self.font.xHeight) / 2);
return titleFrame;
}
- (void)drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView *)controlView
{
NSRect titleRect = [self titleRectForBounds: cellFrame];
[self.attributedStringValue drawInRect: titleRect];
}
The cell is part of an NSOutlineView and hence flipped. For non-flipped content the solution is probably a bit simpler (haven't tested that).
Use setAttributedTitle: of the NSButtonCell class.
Supply it an NSAttributedString
See the Attributed String Programming Guide for guidance. The font will be part of an NSMutableParagraphStyle defining the text styling.
The standard buttons are doing all of this with predefined styling.
Related
I'm having enough trouble with UILabel attributedText that I am now considering creating a UIView subclass that renders its attributed string manually in drawRect.
The gist of the problem I'm having is that UILabels defined in storyboard files always seem to render size 17.0 font at runtime. I've tried a number of hacks to fix this issue, but nothing has worked. No matter what I do, the label's font size remains at 17.0.
The first thing I tried, which I considered to be the most natural solution, was to modify the font size directly in the storyboard. The in-storyboard preview of the label content updated appropriately, but at runtime the font had reset to size 17.0.
The next thing I tried was to adjust the font size at runtime. Some interesting results were observed here: (a) when querying the font size, the label's attributedText property was reporting the correct font size, but it was still rendered as the default size 17.0. Setting the attributed text to something else had no effect. Furthermore, changing the attributed text font was unsuccessful (I tried a number of different things because I wasn't sure if it was just the size that was failing to set, but apparently everything under NSFontAttributeName was immutable.
I also tried replacing the UILabel with a UITextView with user interaction disabled etc, because I thought this might be a bug specific to labels. Once again the text view's attributed text proved to be immutable at runtime, at least from a rendering perspective.
I've had a number of suspicions about what might be causing this issue. I'm curious if it is related to the fact that the label view is within a UITableViewCell subclass, but I don't have time to run a number of experiments. I typically haven't made much use of UILabels with attributed text, but I haven't found on here or elsewhere on the internet evidence of known issues.
I've looked through all the APIs to the best of my abilities, tried disabling certain word wrap settings, and tried setting the Plain label's font size to 14.0 before marking it as attributed because I thought it might be possible the higher-level attributes could be overriding the lower-level attributed text settings. Once again, this approach was not successful.
Has anyone else experienced anything like this?
In the meantime, I will proceed towards implementing my own attributed label as a UIView subclass. If this ends up being the only viable solution for the time being, I'll post the code.
I don't know how you're setting the text in your label, but if you set it with an attributed string that has its font attribute set to what you want, it will work.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RDCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSString *plain = self.theData[indexPath.row];
cell.label.attributedText = [[NSAttributedString alloc] initWithString:plain attributes:#{NSFontAttributeName:[UIFont fontWithName:#"Snell Roundhand" size:24]}];
return cell;
}
I saw the same problems you did when I tried to setup the label in the storyboard to use attributed text.
As discussed, here is the solution I ended up using. It is not as versatile as UILabel in all respects (doesn't support scaling down of fonts etc.) but it turned out to be suitable for my needs.
#interface DHAttributedLabel : UIView
#property (nonatomic, strong) NSAttributedString * attributedText;
#end
#implementation DHAttributedLabel
-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
-(void) drawRect:(CGRect)rect {
CGRect boundingRect = [_attributedText
boundingRectWithSize : CGSizeMake(rect.size.width, CGFLOAT_MAX)
options : NSStringDrawingUsesLineFragmentOrigin
context : nil
];
CGRect drawInRect = rect;
CGFloat dy = .5 * (rect.size.height - boundingRect.size.height);
if (dy > 0) { // center vertically if appropriate
drawInRect.origin.y += dy; // (truncate bottom if the text is too big)
}
[_attributedText drawInRect:drawInRect];
}
#end
I have resolved the same issue by setting attributedText in the UITableViewDataSource cellForRowAtIndexPath: method. Seems that if attributedText property is set via IBOutlet before cell is returned by delegate, some of the attributedText properties are lost (like font size for attributed substring) while others are preserved (like text color for attributed substring).
Seems to me some kind of UIKit issue.
In my case it worked when setting attributedText in tableView(_:willDisplay:forRowAt:).
Attributed Text having HTML content, Means they having their own font specified in their content.
For increasing size in Label attributed Text you need to override the html Font via CSS.
Here is the Content which you get I.e HTMl Content.
<html><body><font face='Helvetica' size='3'>There is no description.</html></body>
As you can see, they have given size in HTML content You need to add extra property to override that font like this.
<style>
html * {font-size: 25.0pt !important;}
</style>
<html><body><font face='Helvetica' size='13'>There is no description.</html></body>
By adding this CSS, try to get attributed String, Set in the Label.
Hope this will increase the font size to 25.0.
I cannot find a way to set the height of a NSProgressIndicator programmatically.
My try so far:
NSProgressIndicator *ind = [[NSProgressIndicator alloc] init];
[ind setStyle: NSProgressIndicatorBarStyle];
// Height does not change height of the actual indicator
[ind setFrame: NSMakeRect(0, 0, 100, 50)];
[ind setBounds: NSMakeRect(0, 0, 100, 50)];
//[ind setControlSize: 0]; does only make it smaller, not bigger
[view addSubview: ind];
I found NSProgressIndicatorBarStyle enumeration in the documentation, but I couldn't find a method to specify the thickness.
Here a screenshot describing my problem: (layer has a background of red for better understanding):
This also occurs when using the NSButton class. Is there a workaround for this?
in iOS You can't change the progress indicator height just changing its frame, due to framework restrictions. However you should be able to achieve the same result, playing with transform
_indicator.transform = CGAffineTransformMakeScale(1.0f, 0.6f);
EDIT: I just tried on Mac OS X
_indicator.layer.transform = CATransform3DMakeScale(1.0f, 0.6f, 0.0f);
and it doesn't work, so it is not like iOS, likely because of how it is implemented on Cocoa (like Ken suggested).
The only way I managed to change the height is using controlSize, but I don't think it will suit your needs (since it doesn't allow you to specify points).
[_indicator setControlSize:NSMiniControlSize]; // or NSSmallControlSize
You should be able to use an arbitrary frame by subclassing NSProgressIndicator, and overriding drawRect, at this point my recommendation would be to look around to find something that can be extended for your use, like this one
https://www.cocoacontrols.com/controls/lbprogressbar
in IB you can
select your NSProgressIndicator control
in the utilities view select the View Effects inspector
press + in Content Filters
select Lanczos Scale Transform filter
set the appropriate scale value in the Scale row
set the Aspect Ratio too if you need to change the height only
this can be added programmatically also, just google for it how to add Content Filters to NSView
I'm trying to implement a custom slider in Cocoa with 5 values. See my demo project, which can be downloaded here: http://s000.tinyupload.com/index.php?file_id=07311576247413689572.
I've subclassed the NSSliderCell and implemented methods like drawKnob:(NSRect)knobRect and drawBarInside:(NSRect)cellFrame flipped:(BOOL)flipped etc.
I'm facing some issues:
I'm not able to position the knob correctly regarding to the background image. I know that I'm able to change the knob's frame, and I've tried doing some calculation to position the knob correctly, but I'm not able to make it work for my custom slider. Could someone please help me with this?
The height of my custom slider background is 41px. In the drawBarInside:(NSRect)cellFrame flipped:(BOOL)flipped I change the height of the frame to 41px as well, but the entire background is not visible. Why?
I've noticed that the included images (the background and knob) are flipped vertically. Why? Note that the border top is darker in the background compared to the bottom, but this is reversed when I draw the background.
I found a mistake in your calculation of the x position of the knob rectangle: You used the height of the image where you should have used the width.
The cell drawing is being clipped to the frame of the control. Maybe you could expand the control frame when your cell awakes.
You need to use the NSImage method drawInRect:fromRect:operation:fraction:respectFlipped:hints:, and pass YES for the respectFlipped: parameter. Apple's controls generally do use flipped coordinates.
Added: Expanding the frame in awakeFromNib doesn't seem to work, the frame gets set back. Here's something that does work. Instead of overriding drawBarInside:flipped:, add this override:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSRect controlFrame = [controlView frame];
float bgHeight = self.backgroundImage.size.height;
if (controlFrame.size.height < bgHeight)
{
controlFrame.size.height = bgHeight;
[controlView setFrame: controlFrame];
}
[self.backgroundImage
drawInRect: [controlView bounds]
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0
respectFlipped: YES
hints: NULL];
[self drawKnob];
}
I use an OHAttributedLabel called demoLbl for displaying text with formatted areas. This label is laid out with Interface Builder and is connected to a property in my ViewController. After setting the attributedText to the label I want all the text to be displayed in the label.
If I don't resize the label then the text is cropped at the end of the label so the rest of the text is missing.
If I use [demoLbl sizeToFit]; then the height of the label is larger or smaller in height than the text (about 10 point, varying with the text's length) thus giving me blank areas at the bottom of my view (after scrolling) plus the width of the label is increased by about 2 points.
If I calculate the height of the original text (NSString) before putting it in a NSAttributedString and adding it to the label's attributedText property then the calculated height is way too small for setting it as the label's height.
Is there a hack or trick I can apply so that the label's height is adjusted according to the NSAttributedString's height?
PS: To be more specific I wanted to add OHAttributedLabel as a tag but it's not allowed to me yet.
I'm the author of OHattributedLabel.
I made some fixes recently about my computation of the size. Please check it out it will probably solve your issue.
I also added a method named sizeConstrainedToSize:fitRange: in NSAttributedString+Attributes.h that returns the CGSize of a given NSAttributedString (quite the same way UIKit's sizeWithFont:constrainedToSize: works, but for Attributed strings and CoreText and not plain stings an UIKit)
Actually OHAttributedLabel's sizeThatFits: calls this method itself now.
You can see if this category gives you a more reliable height.
https://gist.github.com/1071565
Usage
attrLabel.frame.size.height = [attrLabel.attributedString boundingHeightForWidth:attrLabel.frame.size.width];
I added this code to the implementation of the OHAttributedLabel class:
// Toni Soler - 02/09/2011
// Overridden of the UILabel::sizeToFit method
- (void)sizeToFit
{
// Do not call the standard method of the UILabel class, this resizes the frame incorrectly
//[super sizeToFit];
CGSize constraint = CGSizeMake(self.frame.size.width, 20000.0f);
CGRect frame = self.frame;
frame.size = [self sizeThatFits:constraint];
[self setFrame:frame];
}
// End Toni Soler - 02/09/2011
Thank you Olivier for sharing your code!
I have a view that contains a button and a textview. When the button is clicked, the textview's hidden status will change and be shown on the view. Springs and struts have been configured so the textview expands vertically with the view. All this is done in IB
I then insert text into the textview programmatically, but I need the textview to show all its contents without the user needing to scroll.
This is the code I use to calculate the height of the text in the textview:
- (float) getTextViewHeight {
//based on http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html%23//apple_ref/doc/uid/20001809-CJBGBIBB
[textview.textContainer setLineFragmentPadding:0.0];
[textview.layoutManager glyphRangeForTextContainer:textview.textContainer];
return [textview.layoutManager usedRectForTextContainer:self.interactionData.textContainer].size.height;
}
With or without that call to -sizeToFit on the textview, it will either be too big or too small (depending on its contents).
I need to get the height of the textview with all the contents showing so I can adjust the view's size.
I know I could probably use a NSTextField as a label, but I need a NSTextView for its added functionality (specifically using the enclosing scrollview's rulerview).
Does anybody have any suggestions?
NSTextView generally will resize itself if its string over-runs the container width. I think this is because the contained cell has a default behavior for text over-run, called "Line Wrap" or something. My gut feeling is you could just ask the TextView for it's height after it's been loaded and adjust the containing view accordingly, all without needing a layout manager. And obviously make sure the auto-resizing mask is set (oh, you're doing this in IB so no worries there). I could be wrong, and I didn't do any tests... but yeah, you could try it! :P
Her's how I do it and it works well:
// Help text.
NSBundle* mainBundle = [NSBundle mainBundle];
NSString* path = [mainBundle pathForResource: #"category-analysis-help" ofType: #"rtf"];
NSAttributedString* text = [[NSAttributedString alloc] initWithPath: path documentAttributes: NULL];
[helpText setAttributedStringValue: text];
NSRect bounds = [text boundingRectWithSize: NSMakeSize(helpText.bounds.size.width, 0) options: NSStringDrawingUsesLineFragmentOrigin];
helpContentView.frame = NSMakeRect(0, 0, helpText.bounds.size.width + 20, bounds.size.height + 20);
helpContentView is just a container for helpText to add some marging around the text. helpText resizes with its container.
It should be obvious that for the correct height a fixed width is necessary, since the height depends on what fits on the lines.
If you want to omit the scroll view entirely (e.g., make a text view that is attached to another superview and sizes itself to fit its text), you might take a look at NSText. It is, AFAICT, basically a NSTextView without the superview (scroll view parts), and can automagically resize itself.