Autolayout: how does fittingSize work exactly?
cocoa, macos, nslayoutconstraint, objective-c
Solution
`fittingSize` is conceptually simple. It collects all of the constraints that have been added to your view or one of its subviews (recursively), and then it determines the size of the view based on only those constraints. You can think of it as determining the smallest size that is big enough to show the contents of that view hierarchy.
Edit:We need to be quite clear here. `fittingSize` returns minimum values and will return 0 for any dimension that is not fully specified. For example, if the vertical constraint tying a view to the bottom of its superview is omitted then the fitted height will be 0.
Edit: I just realized what you're probably running into: `fittingSize` is computing the text field as if it were just one long line, that does not wrap. You can confirm this by giving it a string value with one or more newlines: now the height should be bigger!
So how to fix this? Well, you have a text field, and you give it contents, and you want to know its height. But if the text field wraps, the height is going to depend on the width: make it narrower, and the text field will wrap to more lines, so it will consume more height. So in order to measure the preferred height for a wrapping text field, you have to tell it the width to wrap at.
On OS X Mountain Lion and iOS 6, you can do that with the `[NSTextField setPreferredMaxLayoutWidth:]` method. For example, if you want to compute the height based on a width of 100, you would call `[textField setPreferredMaxLayoutWidth:100]`; now `fittingSize` will report a height for the text field based on it wrapping at a width of 100.
By the way, this is a bad idea:
[self removeConstraints:self.constraints];
Because it removes constraints that other parts of the system have added. You should only ever remove a constraint that you created, either in code or in IB.
Problem
I have created a subclass of `NSTextField` that changes its height according to the text it contains. I now want to insert it in another view (an `NSTableCellView`) and make the view resize according to the height of the text field. I want to use the `-(NSSize)fittingSize` method of `NSView` but unfortunately it doesn't seem to call the `fittingSize` method of its subviews nor their `intrinsicContentSize` method. Here is the code I use for the `NSTableCellView` subclass: ``` - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { [self setTranslatesAutoresizingMaskIntoConstraints:NO]; self.expandingTextField = [[JSExpandingTextField alloc] init]; [self addSubview:self.expandingTextField]; [self removeConstraints:self.constraints]; NSDictionary *row = NSDictionaryOfVariableBindings(expandingTextField); [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[expandingTextField]-28-|" options:0 metrics:nil views:row]]; } return self; } - (NSSize)fittingSize { return [super fittingSize]; } ``` I override the `fittingSize` method here only to put a breakpoint or an `NSLog`. Here is the code of the table view delegate that provides the height of the table cell: ``` - (JSDynamicTableCellView *)dummyCell { if (!_dummyCell) { _dummyCell = [[JSDynamicTableCellView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 100, 100)]; } return _dummyCell; } - (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row { self.dummyCell.expandingTextField.stringValue = @"Test"; NSLog(@"Cell size %@",NSStringFromSize([self.dummyCell fittingSize])); return [self.dummyCell fittingSize].height; } ``` All of this always returns an height for dummyCell of 69 independent of the size of the expanding textfield in the cell. The question is: how does the 'fittingSize' method figure out the size of its subviews? Should it call their 'fittingSize' or 'ntrinsicContentSize' methods or is it something else?