How to let NSTextField grow with the text in auto layout?
Solution 1:
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];
}
Solution 2:
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.
Solution 3:
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.