Is there a "right" way to have NSTextFieldCell draw vertically centered text?

I have an NSTableView with several text columns. By default, the dataCell for these columns is an instance of Apple's NSTextFieldCell class, which does all kinds of wonderful things, but it draws text aligned with the top of the cell, and I want the text to be vertically centered in the cell.

There is an internal flag in NSTextFieldCell that can be used to vertically center the text, and it works beautifully. However, since it is an internal flag, its use is not sanctioned by Apple and it could simply disappear without warning in a future release. I am currently using this internal flag because it is simple and effective. Apple has obviously spent some time implementing the feature, so I dislike the idea of re-implementing it.

So; my question is this: What is the right way to implement something that behaves exactly like Apple's NStextFieldCell, but draws vertically centered text instead of top-aligned?

For the record, here is my current "solution":

@interface NSTextFieldCell (MyCategories)
- (void)setVerticalCentering:(BOOL)centerVertical;
@end

@implementation NSTextFieldCell (MyCategories)
- (void)setVerticalCentering:(BOOL)centerVertical
{
    @try { _cFlags.vCentered = centerVertical ? 1 : 0; }
    @catch(...) { NSLog(@"*** unable to set vertical centering"); }
}
@end

Used as follows:

[[myTableColumn dataCell] setVerticalCentering:YES];

Answers


The other answers didn't work for multiple lines. Therefore I initially continued using the undocumented cFlags.vCentered property, but that caused my app to be rejected from the app store. I ended up using a modified version of Matt Bell's solution that works for multiple lines, word wrapping, and a truncated last line:

-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
    NSAttributedString *attrString = self.attributedStringValue;

    /* if your values can be attributed strings, make them white when selected */
    if (self.isHighlighted && self.backgroundStyle==NSBackgroundStyleDark) {
        NSMutableAttributedString *whiteString = attrString.mutableCopy;
        [whiteString addAttribute: NSForegroundColorAttributeName
                            value: [NSColor whiteColor]
                            range: NSMakeRange(0, whiteString.length) ];
        attrString = whiteString;
    }

    [attrString drawWithRect: [self titleRectForBounds:cellFrame] 
                     options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin];
}

- (NSRect)titleRectForBounds:(NSRect)theRect {
    /* get the standard text content rectangle */
    NSRect titleFrame = [super titleRectForBounds:theRect];

    /* find out how big the rendered text will be */
    NSAttributedString *attrString = self.attributedStringValue;
    NSRect textRect = [attrString boundingRectWithSize: titleFrame.size
                                               options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin ];

    /* If the height of the rendered text is less then the available height,
     * we modify the titleRect to center the text vertically */
    if (textRect.size.height < titleFrame.size.height) {
        titleFrame.origin.y = theRect.origin.y + (theRect.size.height - textRect.size.height) / 2.0;
        titleFrame.size.height = textRect.size.height;
    }
    return titleFrame;
}

(This code assumes ARC; add an autorelease after attrString.mutableCopy if you use manual memory management)


Overriding NSCell's -titleRectForBounds: should do it -- that's the method responsible for telling the cell where to draw its text:

- (NSRect)titleRectForBounds:(NSRect)theRect {
    NSRect titleFrame = [super titleRectForBounds:theRect];
    NSSize titleSize = [[self attributedStringValue] size];
    titleFrame.origin.y = theRect.origin.y + (theRect.size.height - titleSize.height) / 2.0;
    return titleFrame;
}

- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
    NSRect titleRect = [self titleRectForBounds:cellFrame];
    [[self attributedStringValue] drawInRect:titleRect];
}

FYI, this works well, although I haven't managed to get it to stay centered when you edit the cell... I sometimes have cells with large amounts of text and this code can result in them being misaligned if the text height is greater then the cell it's trying to vertically center it in. Here's my modified method:

- (NSRect)titleRectForBounds:(NSRect)theRect 
 {
    NSRect titleFrame = [super titleRectForBounds:theRect];
    NSSize titleSize = [[self attributedStringValue] size];
     // test to see if the text height is bigger then the cell, if it is,
     // don't try to center it or it will be pushed up out of the cell!
     if ( titleSize.height < theRect.size.height ) {
         titleFrame.origin.y = theRect.origin.y + (theRect.size.height - titleSize.height) / 2.0;
     }
    return titleFrame;
}

For anyone attempting this using Matt Ball's drawInteriorWithFrame:inView: method, this will no longer draw a background if you have set your cell to draw one. To solve this add something along the lines of

[[NSColor lightGrayColor] set];
NSRectFill(cellFrame);

to the beginning of your drawInteriorWithFrame:inView: method.


Though this is pretty old question...

I believe default style of NSTableView implementation is intended strictly for single line text display with all same size & font.

In that case, I recommend,

  1. Set font.
  2. Adjust rowHeight.

Maybe you will get quietly dense rows. And then, give them padding by setting intercellSpacing.

For example,

    core_table_view.rowHeight               =   [NSFont systemFontSizeForControlSize:(NSSmallControlSize)] + 4;
    core_table_view.intercellSpacing        =   CGSizeMake(10, 80);

Here what you'll get with two property adjustment.

This won't work for multi-line text, but very good enough for quick vertical center if you don't need multi-line support.


I had the same problem and here is the solution I did :

1) In Interface Builder, select your NSTableCellView. Make sure it as big as the row height in the Size Inspector. For example, if your row height is 32, make your Cell height 32

2) Make sure your cell is well placed in your row (I mean visible)

3) Select your TextField inside your Cell and go to your size inspector

4) You should see "Arrange" item and select "Center Vertically in Container"

--> The TextField will center itself in the cell


No. The right way is to put the Field in another view and use auto layout or that parent view's layout to position it.


Need Your Help

Useful keyboard shortcuts and tips for ESS/R

emacs r elisp ess

I would like to ask regular ESS/R users what key bindings do they use frequently and tips on using ESS/R.

Why is lazy evaluation useful?

haskell functional-programming lazy-evaluation

I have long been wondering why lazy evaluation is useful. I have yet to have anyone explain to me in a way that makes sense; mostly it ends up boiling down to "trust me".