views:

622

answers:

3

I have a NSTextFieldCell that I wish to display with middle vertical alignment. Thanks to an older question here and a blog entry I have two working solutions.

However, both solutions seem to squash my ability to set the cell as right aligned. Can anyone help me make either of these solutions support both forms of alignment?

Here is the code for one solution:

@implementation MiddleAlignedTextFieldCell

- (NSRect)titleRectForBounds:(NSRect)theRect {
    NSRect titleFrame = [super titleRectForBounds:theRect];
    NSSize titleSize = [[self attributedStringValue] size];
    titleFrame.origin.y = theRect.origin.y - .5 + (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];
}

@end

The alternative solution is (obtained from this blog):

@implementation RSVerticallyCenteredTextFieldCell

- (NSRect)drawingRectForBounds:(NSRect)theRect
{
    NSRect newRect = [super drawingRectForBounds:theRect];

    if (mIsEditingOrSelecting == NO)
    {
     // Get our ideal size for current text
     NSSize textSize = [self cellSizeForBounds:theRect];

     // Center that in the proposed rect
     float heightDelta = newRect.size.height - textSize.height; 
     if (heightDelta > 0)
     {
      newRect.size.height -= heightDelta;
      newRect.origin.y += (heightDelta / 2);
     }
    }

    return newRect;
}

- (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(int)selStart length:(int)selLength
{
    aRect = [self drawingRectForBounds:aRect];
    mIsEditingOrSelecting = YES; 
    [super selectWithFrame:aRect inView:controlView editor:textObj delegate:anObject start:selStart length:selLength];
    mIsEditingOrSelecting = NO;
}

- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent
{   
    aRect = [self drawingRectForBounds:aRect];
    mIsEditingOrSelecting = YES;
    [super editWithFrame:aRect inView:controlView editor:textObj delegate:anObject event:theEvent];
    mIsEditingOrSelecting = NO;
}

@end
+1  A: 

You can use NSParagraphStyle/NSMutableParagraphStyle to set the alignment (and other attributes). Add an appropriately-configured NSParagraphStyle object to the full range of your attributed string.

Joshua Nozzi
That requires me to couple in that portion of the presentation with my data layer, and honestly I'm not sure that I like that. This should respect the options presented in IB, but for some reasons it not (for that matter vertical alignment should also be an option in IB).
Bryan McLemore
I'm not sure how it's mixing it any more than setting NSTextFieldCell's string to something from your model layer. :-) Alignment (and font, and color, and size, ...) is part of all of this. I agree, however, that vertical alignment should be an option - file an enhancement report. Since it's not available, you'll have to account for it in the drawing code.
Joshua Nozzi
Er, that's "enhancement request". Link: http://bugreporter.apple.com
Joshua Nozzi
A: 

There are a couple of potential solutions posted in a similar question which I asked a while back.

In all honesty, I still use the undocumented _cFlags.vCentered boolean (tsk tsk, bad programmer!) to get the job done. It's simple, and it works. I'll reinvent the wheel later on if I have to.

update:

OK, I think I've figured it out. Both solutions rely on a call to super to get the default rect, and then modify origin.y and size.height to perform the vertical centering. The calls to super, however, return a rectangle whose width has already been adjusted to fit the text horizontally.

The solution is to use origin.x and size.width from the bounds rect that is passed in to the method:

In solution #1:

- (NSRect)titleRectForBounds:(NSRect)theRect {
    NSRect titleFrame = [super titleRectForBounds:theRect];
    NSSize titleSize = [[self attributedStringValue] size];

    // modified:
    theRect.origin.y += (theRect.size.height - titleSize.height)/2.0 - 0.5;
    return theRect;
}

In solution #2:

- (NSRect)drawingRectForBounds:(NSRect)theRect
{
    NSRect newRect = [super drawingRectForBounds:theRect];

    // modified:
    newRect.origin.x = theRect.origin.x;
    newRect.size.width = theRect.size.width;

    if (mIsEditingOrSelecting == NO)
    {
        // Get our ideal size for current text
        NSSize textSize = [self cellSizeForBounds:theRect];

        // Center that in the proposed rect
        float heightDelta = newRect.size.height - textSize.height;  
        if (heightDelta > 0)
        {
            newRect.size.height -= heightDelta;
            newRect.origin.y += (heightDelta / 2);
        }
    }

    return newRect;
}
e.James
It was actually the solution to that question that I found one of my solutions.
Bryan McLemore
I'm just trying to figure out how to maintain the horizontal alignment with this solution. But I may have to go down and try another route if I can't.
Bryan McLemore
I'm surprised that the horizontal alignment is affected by either of these solutions at all, since neither of them messes with the x dimension. How are you setting the alignment?
e.James
In Interface Builder, both on the Column and the Cell.
Bryan McLemore
Thanks for the update on the solution james, but it still is failing to right align it. I did confirm that _cFlags.alignment is being set properly. It may also be worth noting that center align still works fine.
Bryan McLemore
I've managed to come up with a working 'fix'. Although it involves examining the value of _cFlags.alignment.
Bryan McLemore
A: 

I'm posting this answer to the question since it does work, however, I find the fact that I couldn't find another way to check the alignment setting from IB is very annoying. Accessing _cFlags just seems a little dirty, and I'd love to find a cleaner method.

Based on the code posted earlier from this blog entry.

- (NSRect)drawingRectForBounds:(NSRect)theRect
{
    // Get the parent's idea of where we should draw
    NSRect newRect = [super drawingRectForBounds:theRect];

    if (mIsEditingOrSelecting == NO)
    {
     // Get our ideal size for current text
     NSSize textSize = [self cellSizeForBounds:theRect];

     // Center that in the proposed rect
     float heightDelta = newRect.size.height - textSize.height; 
     if (heightDelta > 0)
     {
      newRect.size.height -= heightDelta;
      newRect.origin.y += (heightDelta / 2);
     }

     // For some reason right aligned text doesn't work.  This section makes it work if set in IB.
     // HACK: using _cFlags isn't a great idea, but I couldn't find another way to find the alignment.
     // TODO: replace _cFlags usage if a better solution is found.
     float widthDelta = newRect.size.width - textSize.width;
     if (_cFlags.alignment == NSRightTextAlignment && widthDelta > 0) {
      newRect.size.width -= widthDelta;
      newRect.origin.x += widthDelta;
     }

    }

    return newRect;
}
Bryan McLemore