I\'ve got a view that contains only a UILabel. This label contains multiline text. The parent has a variable width that can be resized with a pan gesture. My problem is t
I've fixed this issue after raising a bug with Apple. The issue that multiline text requires a two-pass approach to layout and it all relies on the property preferredMaxLayoutWidth
Here is the relevant code that needs to be added to a view controller that contains a multiline label:
- (void)viewWillLayoutSubviews
{
// Clear the preferred max layout width in case the text of the label is a single line taking less width than what would be taken from the constraints of the left and right edges to the label's superview
[self.label setPreferredMaxLayoutWidth:0.];
}
- (void)viewDidLayoutSubviews
{
// Now that you know what the constraints gave you for the label's width, use that for the preferredMaxLayoutWidth—so you get the correct height for the layout
[self.label setPreferredMaxLayoutWidth:[self.label bounds].size.width];
// And then layout again with the label's correct height.
[self.view layoutSubviews];
}
In Xcode 6.1 for iOS 7/8, I was able to get this to work by just setting preferredMaxLayoutWidth
in a setter method that's called on my view to display the text for the label. I'm guessing it was set to 0
to begin with. Example below, where self.teachPieceLabel
is the label. The label is wired up with constraints alongside other labels in a view in Interface Builder.
- (void)setTeachPieceText:(NSString *)teachPieceText {
self.teachPieceLabel.text = teachPieceText;
[self.teachPieceLabel setPreferredMaxLayoutWidth:[self.teachPieceLabel bounds].size.width];
}
Okay, I finally nailed it. The solution is to set the preferredMaxLayoutWidth
in viewDidLayoutSubviews
, but only after the first round of layout. You can arrange this simply by dispatching asynchronously back onto the main thread. So:
- (void)viewDidLayoutSubviews {
dispatch_async(dispatch_get_main_queue(), ^{
self.theLabel.preferredMaxLayoutWidth = self.theLabel.bounds.size.width;
});
}
That way, you don't set preferredMaxLayoutWidth
until after the label's width has been properly set by its superview-related constraints.
Working example project here:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ch23p673selfSizingLabel4/p565p579selfSizingLabel/ViewController.m
EDIT: Another approach! Subclass UILabel and override layoutSubviews
:
- (void) layoutSubviews {
[super layoutSubviews];
self.preferredMaxLayoutWidth = self.bounds.size.width;
}
The result is a self-sizing label - it automatically changes its height to accommodate its contents no matter how its width changes (assuming its width is changed by constraints / layout).