40

Auto layout in Lion should make it fairly simple to let a text field (and hence a label) grow with text it holds.

The text field is set to wrap in Interface Builder.

What is a simple and reliable way to do this?

Monolo
  • 18,205
  • 17
  • 69
  • 103

3 Answers3

52

The method intrinsicContentSize in NSView returns what the view itself thinks of as its intrinsic content size.

NSTextField calculates this without considering the wraps property of its cell, so it will report the dimensions of the text if laid out in on a single line.

Hence, a custom subclass of NSTextField can override this method to return a better value, such as the one provided by the cell's cellSizeForBounds: method:

-(NSSize)intrinsicContentSize
{
    if ( ![self.cell wraps] ) {
        return [super intrinsicContentSize];
    }

    NSRect frame = [self frame];

    CGFloat width = frame.size.width;

    // Make the frame very high, while keeping the width
    frame.size.height = CGFLOAT_MAX;

    // Calculate new height within the frame
    // with practically infinite height.
    CGFloat height = [self.cell cellSizeForBounds: frame].height;

    return NSMakeSize(width, height);
}

// you need to invalidate the layout on text change, else it wouldn't grow by changing the text
- (void)textDidChange:(NSNotification *)notification
{
    [super textDidChange:notification];
    [self invalidateIntrinsicContentSize];
}
Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179
Monolo
  • 18,205
  • 17
  • 69
  • 103
  • 2
    This exactly matches what I am seeing. Just to clarify, this ignoring of the 'wraps' property on the cell appears to be a bug in Lion which has been fixed in Mountain Lion. So, if you are targeting Lion and above you will see different results in the 2 OSes until you apply the bugfix to bring Lion up to speed. – Scott Roth Oct 27 '12 at 14:30
  • Yes, this is a bug in Lion. I really wish Apple would fix these issues in both OS X 10.7 && 10.8. In my implementation, I do some more checks for special cases (e.g. If it is Lion || If language is not English) and I set my height a little different. Nevertheless, what Monolo posted is a good start. – Arvin Nov 12 '12 at 14:52
  • 2
    The intrinsicContentSize: method is not called when you change the text in it by setStringValue:. – ideawu Jan 11 '14 at 06:02
  • you need to invalidate the layout on - (void)textDidChange:(NSNotification *)notification { [super textDidChange:notification]; [self invalidateIntrinsicContentSize]; } – Peter Lapisu Oct 04 '14 at 19:12
  • Has anyone made a Swift version of this lately? – Clifton Labrum Nov 08 '17 at 22:07
  • Thank you for this answer Peter, it did exactly what I needed and very elegantly via the intrinsic size. I am using Cocoa Bindings and using your method solves a number of headaches automatically! I have included a Swift 4 version below. – Lore Mar 23 '18 at 13:42
9

Swift 4

Editable Autosizing NSTextField

Based on Peter Lapisu's Objective-C post

Subclass NSTextField, add the code below.

override var intrinsicContentSize: NSSize {
    // Guard the cell exists and wraps
    guard let cell = self.cell, cell.wraps else {return super.intrinsicContentSize}

    // Use intrinsic width to jive with autolayout
    let width = super.intrinsicContentSize.width

    // Set the frame height to a reasonable number
    self.frame.size.height = 750.0

    // Calcuate height
    let height = cell.cellSize(forBounds: self.frame).height

    return NSMakeSize(width, height);
}

override func textDidChange(_ notification: Notification) {
    super.textDidChange(notification)
    super.invalidateIntrinsicContentSize()
}

Setting self.frame.size.height to 'a reasonable number' avoids some bugs when using FLT_MAX, CGFloat.greatestFiniteMagnitude or large numbers. The bugs occur during operation when the user select highlights the text in the field, they can drag scroll up and down off into infinity. Additionally when the user enters text the NSTextField is blanked out until the user ends editing. Finally if the user has selected the NSTextField and then attempts to resize the window, if the value of self.frame.size.height is too large the window will hang.

Lore
  • 680
  • 8
  • 8
4

The accepted answer is based on manipulating intrinsicContentSize but that may not be necessary in all cases. Autolayout will grow and shrink the height of the text field if (a) you give the text field a preferredMaxLayoutWidth and (b) make the field not editable. These steps enable the text field to determine its intrinsic width and calculate the height needed for autolayout. See this answer and this answer for more details.

Even more obscurely, it follows from the dependency on the text field's editable attribute that autolayout will break if you are using bindings on the field and fail to clear the Conditionally Sets Editable option.

Community
  • 1
  • 1
spinacher
  • 549
  • 6
  • 10