views:

811

answers:

4

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];
+2  A: 

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];
}
Matt Ball
This seems like a promising idea, but it doesn't work for me. I created a subclass of `NSTextFieldCell`, copied your code in, and used the custom class for the cells in my `NSTableView`, but the `titleRectForBounds` method is never called.
e.James
Ah, I forgot that NSTextFieldCell basically lies in its documentation -- you're right, NSTextFieldCell by default doesn't use -titleRectForBounds:. I've updated my answer to override -drawInteriorWithFrame:inView: in order to draw the text at the right spot.
Matt Ball
Nicely done. That works like a charm!
e.James
A: 

I went through the same thing last night. I saw the _cFlags.vCentered variable in the debugger also and am frustrated that there seems to be no documented way to access it. Changing it there does exactly what one would want! Sigh. Anyway, glad to know it's not just me. I'm glad I found your post.

I've tried using the solution of a subclass of NSTextFieldCell and it works fine for centering, but I'm unable to highlight a cell anymore:

- (void) setHighlightForCell: (int) cellNum  highlight: (BOOL) h {
   int columnCount = [outputMatrix numberOfColumns];
   NSCell *theCell = [outputMatrix cellAtRow: (cellNum / columnCount) column: (cellNum % columnCount)];
   [theCell setHighlighted: h];
}

Any ideas?

Greg Hartwig
+1  A: 

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;
}
coradine
+1  A: 

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.

Greg Sexton