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 that when I do this resizing the UILabel does not recalculate its height such that all of the content is still visible, it simply cuts it off.
I've managed to fix it with a bit of a hack but it is horribly slow to run:
- (void)layoutSubviews { CGSize labelSize = [self.labelDescription sizeThatFits:CGSizeMake(self.frame.size.width, FLT_MAX)]; if (self.constraintHeight) { [self removeConstraint:self.constraintHeight]; } self.constraintHeight = [NSLayoutConstraint constraintWithItem:self.labelDescription attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:labelSize.height]; [self addConstraint:self.constraintHeight]; [super layoutSubviews]; }
Shouldn't this happen automatically with autolayout?
EDIT
The structure of my view is:
UIScrollView ---> UIView ---> UILabel
Here are the constraints on the UIScrollView:
<NSLayoutConstraint:0x120c4860 H:|-(>=32)-[DescriptionView:0x85c81c0] (Names: '|':UIScrollView:0x85db650 )>, <NSLayoutConstraint:0x120c48a0 H:|-(32@900)-[DescriptionView:0x85c81c0] priority:900 (Names: '|':UIScrollView:0x85db650 )>, <NSLayoutConstraint:0x120c48e0 H:[DescriptionView:0x85c81c0(<=576)]>, <NSLayoutConstraint:0x120c4920 H:[DescriptionView:0x85c81c0]-(>=32)-| (Names: '|':UIScrollView:0x85db650 )>, <NSLayoutConstraint:0x120c4960 H:[DescriptionView:0x85c81c0]-(32@900)-| priority:900 (Names: '|':UIScrollView:0x85db650 )>, <NSLayoutConstraint:0x8301450 DescriptionView:0x85c81c0.centerX == UIScrollView:0x85db650.centerX>,
Here are the constraints on the UIView:
<NSLayoutConstraint:0x85c4580 V:|-(0)-[UILabel:0x85bc7b0] (Names: '|':DescriptionView:0x85c81c0 )>, <NSLayoutConstraint:0x85c45c0 H:|-(0)-[UILabel:0x85bc7b0] (Names: '|':DescriptionView:0x85c81c0 )>, <NSLayoutConstraint:0x85c9f80 UILabel:0x85bc7b0.trailing == DescriptionView:0x85c81c0.trailing>, <NSLayoutConstraint:0x85c9fc0 UILabel:0x85bc7b0.centerY == DescriptionView:0x85c81c0.centerY>
The UILabel itself has no constraints on it, aside from pinning it to the edges of its parent
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).
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]; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With