cocoanstextfieldnscellnstextfieldcell

NSTextFieldCell's cellSizeForBounds: doesn't match wrapping behavior?


It seems to be commonly accepted that cellSizeForBounds: allows one to calculate a text field's "natural" size. However, for NSTextField, I've found that it doesn't quite match:

@interface MyTextField : NSTextField @end
@implementation MyTextField
- (void)textDidChange:(NSNotification *)notification {
    [super textDidChange:notification];
    [self validateEditing];  // Forces updating from the field editor

    NSSize cellSize = [self.cell cellSizeForBounds:
                       NSMakeRect(0, 0, self.bounds.size.width, CGFLOAT_MAX)];
    NSRect frame = self.frame;
    CGFloat heightDelta = cellSize.height - frame.size.height;
    frame.size.height += heightDelta;
    if (!self.superview.flipped) { frame.origin.y -= heightDelta; }
    self.frame = frame;
}
@end

(Note that I'm not using Auto Layout, but the principle is the same. This problem doesn't happen with every string, but it is pretty easy to reproduce.)

I suspect this is because of the text field's border, which adds an extra offset. Is there any way to automatically compute the relationship between cellSizeForBounds: and the NSTextField's frame? How else might I solve this issue?


Solution

  • There is a difference between the cell size and the frame size. The best way to determine it is to ask a text field to size itself to its content using -sizeToFit and then compare its cell size to its frame size. You may want to do this with a secondary, off-screen text field. Be sure to configure all of its parameters identically to the text field you're intending to resize. Also, the cell size will be an "exact" fit, meaning it will potentially have fractional width or height. The frame size resulting from -sizeToFit will be integral. So, you should apply ceil() to the cell size components before comparing to the frame size to compute the border size.

    In theory, you only have to do this once for a given text field configuration / style.