iOS AutoLayout multi-line UILabel

Solution 1:

There's an answer this question on objc.io in the "Intrinsic Content Size of Multi-Line Text" section of Advanced Auto Layout Toolbox. Here's the relevant info:

The intrinsic content size of UILabel and NSTextField is ambiguous for multi-line text. The height of the text depends on the width of the lines, which is yet to be determined when solving the constraints. In order to solve this problem, both classes have a new property called preferredMaxLayoutWidth, which specifies the maximum line width for calculating the intrinsic content size.

Since we usually don’t know this value in advance, we need to take a two-step approach to get this right. First we let Auto Layout do its work, and then we use the resulting frame in the layout pass to update the preferred maximum width and trigger layout again.

The code they give for use inside a view controller:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
    [self.view layoutIfNeeded];
}

Take a look at their post, there's more information about why it's necessary to do the layout twice.

Solution 2:

It seems annoying that a UILabel doesn't default to its width for the preferred max layout width, if you've got constraints that are unambiguously defining that width for you.

In nearly every single case I've used labels under Autolayout, the preferred max layout width has been the actual width of the label, once the rest of my layout has been performed.

So, to make this happen automatically, I have used a UILabel subclass, which overrides setBounds:. Here, call the super implementation, then, if it isn't the case already, set the preferred max layout width to be the bounds size width.

The emphasis is important - setting preferred max layout causes another layout pass to be performed, so you can end up with an infinite loop.

Solution 3:

Update

My original answer appears to be helpful so I have left it untouched below, however, in my own projects I have found a more reliable solution that works around bugs in iOS 7 and iOS 8. https://github.com/nicksnyder/ios-cell-layout

Original answer

This is a complete solution that works for me on iOS 7 and iOS 8

Objective C

@implementation AutoLabel

- (void)setBounds:(CGRect)bounds {
  if (bounds.size.width != self.bounds.size.width) {
    [self setNeedsUpdateConstraints];
  }
  [super setBounds:bounds];
}

- (void)updateConstraints {
  if (self.preferredMaxLayoutWidth != self.bounds.size.width) {
    self.preferredMaxLayoutWidth = self.bounds.size.width;
  }
  [super updateConstraints];
}

@end

Swift

import Foundation

class EPKAutoLabel: UILabel {

    override var bounds: CGRect {
        didSet {
            if (bounds.size.width != oldValue.size.width) {
                self.setNeedsUpdateConstraints();
            }
        }
    }

    override func updateConstraints() {
        if(self.preferredMaxLayoutWidth != self.bounds.size.width) {
            self.preferredMaxLayoutWidth = self.bounds.size.width
        }
        super.updateConstraints()
    }
}