Automatically wrap NSTextField using Auto Layout

The simplest way to get this working, assuming you're using an NSViewController-based solution is this:

- (void)viewDidLayout {
    [super viewDidLayout];
    self.aTextField.preferredMaxLayoutWidth = self.aTextField.frame.size.width;
    [self.view layoutSubtreeIfNeeded];
}

This simply lets the constraint system solve for the width (height will be unsolvable on this run so will be what ever you initially set it to), then you apply that width as the max layout width and do another constraint based layout pass.

No subclassing, no mucking with a view's layout methods, no notifications. If you aren't using NSViewController you can tweak this solution so that it works in most cases (subclassing textfield, in a custom view, etc.).

Most of this came from the swell http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html (look at the Intrinsic Content Size of Multi-Line Text section).


Set in the size inspector tab in section Text Field Preferred Width to "First Runtime layout Width"


If inspector pane width will never change, just check "First Runtime Layout Width" in IB (note it's 10.8+ feature).

But allowing inspector to have variable width at the same time is not possible to achieve with constraints alone. There is a weak point somewhere in AutoLayout regarding this.

I was able to achieve reliable behaviour by subclassing the text field like this:

- (NSSize) intrinsicContentSize;
{
    const CGFloat magic = -4;

    NSSize rv;
    if ([[self cell] wraps] && self.frame.size.height > 1)
        rv = [[self cell] cellSizeForBounds:NSMakeRect(0, 0, self.bounds.size.width + magic, 20000)];
    else
        rv = [super intrinsicContentSize];
    return rv;
}

- (void) layout;
{
    [super layout];
    [self invalidateWordWrappedContentSizeIfNeeded];
}

- (void) setFrameSize:(NSSize)newSize;
{
    [super setFrameSize:newSize];
    [self invalidateWordWrappedContentSizeIfNeeded];
}

- (void) invalidateWordWrappedContentSizeIfNeeded;
{
    NSSize a = m_previousIntrinsicContentSize;
    NSSize b = self.intrinsicContentSize;
    if (!NSEqualSizes(a, b))
    {
        [self invalidateIntrinsicContentSize];
    }
    m_previousIntrinsicContentSize = b;
}

In either case, the constraints must be set the obvious way (you have probably already tried it): high vertical hugging priority, low horizontal, pin all four edges to superview and/or sibling views.