James Bucanek
James Bucanek

Reputation: 3439

NSTextField intrinsicContentSize is always returning { NSViewNoInstrinsicMetric, 15 }, width does not resize

I've got a pair of editable text fields in a view using auto layout. (For completeness, these are inside an NSTableRowView in an editable table, but I don't think that should matter.)

Editable text fields in a table row view

I'd like the fields to resize their width based on their content, especially once they are edited.

However, NSTextField is not cooperating. I added some code to log the intrinsicContentSize whenever the edit field ends editing, and the value is always { NSViewNoInstrinsicMetric, 15 }, meaning the field has no intrinsic width.

I've seen a lot of posts about multi-line NSTextField and vertical (height) resizing, wrapping, and so on, but I can't find any questions or answers that apply here. The fields are both set to "use single line", "clip", and "scroll".

I've tried sending the fields updateConstraints and invalidateIntrinsicContentSize messages, but they don't seem to have any effect.

I know I can create a subclass of NSTextField and hack this, but I don't understand why this isn't working in the first place. Or maybe NSTextField simply doesn't have an intrinsic width, but I can't find that documented anywhere.

Note: previous answers to just set field.editable = NO are insufficient; these fields must be editable.

Upvotes: 1

Views: 440

Answers (1)

James Bucanek
James Bucanek

Reputation: 3439

Here's my hack:

//
// Create a special text field subclass that provides an intrinsic width for its content.
// Editable NSTextFields normally do not have an intrinsic width, because I guess that would just be too weird.
// This field returns an intrinsic width when not being edited, and the width of its superview when it is.
//

@interface ResizingPatternTextField : NSTextField
@end

@implementation ResizingPatternTextField

- (BOOL)becomeFirstResponder
{
    BOOL willEdit = [super becomeFirstResponder];
    if (willEdit)
        [self invalidateIntrinsicContentSize];
    return willEdit;
}

- (void)textDidEndEditing:(NSNotification*)notification
{
    [super textDidEndEditing:notification];
    [self invalidateIntrinsicContentSize];
}

- (NSSize)intrinsicContentSize
{
    NSSize intrinsiceSize = super.intrinsicContentSize;
    if (self.currentEditor!=nil)
        {
        // The field is currently being edited: return the width of the superview as the intrinsic width
        // This should cause the field to expand to it's maximum width, within the constraints of the layout
        intrinsiceSize.width = self.superview.bounds.size.width;
        }
    else
        {
        // If the field isn't being edited and it's editable: calculate the width of the field ourselves
        if (self.editable)
            {
            NSDictionary* textAttrs = @{ NSFontAttributeName: self.font };
            NSSize textSize = [self.stringValue sizeWithAttributes:textAttrs];
            // Return an intrinsic size with a little padding, rounded up to the nearest whole integer
            intrinsiceSize.width = CGCeiling(textSize.width+7.0);
            }
        }
    return intrinsiceSize;
}

Notes:

  • The padding value of 7.0 points is fixed (for a "small" control size) and isn't a general solution
  • CGCeiling is just a macro for ceil()
  • You have to make sure the hugging, compression resistance, and other layout constraints present a pleasing layout when the intrinsic width is very wide. (You should do this anyway, but here it's extra important.)

Upvotes: 1

Related Questions