So I have my view setup in IB such that this text label aligns with the top of the thumbnail via constraints.
This problem is a real PITA to solve. It doesn't help that the API's that work are deprecated in iOS7, or that the iOS7 replacement API's are broken. Blah!
Your solution is nice, however it uses a deprecated API (sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:
), and it's not very well encapsulated - you need to copy this code around to any cells or views where you want this behavior. On the plus side it's fairly efficient! One bug may be that the label hasn't been laid out yet when you do your calculation, but you perform your calculation based on its width.
I propose that you encapsulate this behavior in a UILabel subclass. By placing the sizing calculation in an overridden intrinsicContentSize
method the label will auto-size itself. I wrote the following, which incorporates your code that will execute on iOS6, and my version using non-deprecated API's for iOS7 or better:
@implementation TSAutoHeightLabel
- (CGSize) intrinsicContentSize
{
NSAssert( self.baselineAdjustment == UIBaselineAdjustmentAlignCenters, @"Please ensure you are using UIBaselineAdjustmentAlignCenters!" );
NSAssert( self.numberOfLines == 1, @"This is only for single-line labels!" );
CGSize intrinsicContentSize;
if ( [self.text respondsToSelector: @selector( boundingRectWithSize:options:attributes:context: )] )
{
NSStringDrawingContext* context = [NSStringDrawingContext new];
context.minimumScaleFactor = self.minimumScaleFactor;
CGSize inaccurateSize = [self.text boundingRectWithSize: CGSizeMake( self.bounds.size.width, CGFLOAT_MAX )
options: NSStringDrawingUsesLineFragmentOrigin
attributes: @{ NSFontAttributeName : self.font }
context: context].size;
CGSize accurateSize = [self.text sizeWithAttributes: @{ NSFontAttributeName : [UIFont fontWithName: self.font.fontName size: 12.0] } ];
CGFloat accurateHeight = accurateSize.height * inaccurateSize.width / accurateSize.width;
intrinsicContentSize = CGSizeMake( inaccurateSize.width, accurateHeight);
}
else
{
CGFloat actualFontSize;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[self.text sizeWithFont: self.font
minFontSize: self.minimumFontSize
actualFontSize: &actualFontSize
forWidth: self.frame.size.width
lineBreakMode: NSLineBreakByTruncatingTail];
#pragma GCC diagnostic pop
CGRect lineBox = CTFontGetBoundingBox((__bridge CTFontRef)([UIFont fontWithName: self.font.fontName size: actualFontSize]));
intrinsicContentSize = lineBox.size;
}
return intrinsicContentSize;
}
@end
This implementation isn't perfect. I had to ensure using baselineAdjustment == UIBaselineAdjustmentAlignCenters, and I'm not 100% certain I understand why. And I'm not happy with the hoops I had to jump through to get an accurate text height. There's also a few pixel difference between what my calculation produces, and yours. Feel free to play with it and adjust as necessary :)
The boundingRectWithSize:options:attributes:context
API seems pretty broken to me. While it (mostly!) correctly constrains the text to the input size, it doesn't calculate the correct height! The height it returns is based on the line-height of the supplied font, even if a scaling is in play. My guess is this is why UILabel
doesn't have this behavior by default? My workaround is to calculate an unconstrained size where both the height and width are accurate, then use the ratio between the constrained and unconstrained widths to calculate the accurate height for the constrained size. What a PITA. There are lots of complaints in the Apple dev forums and here on SO that point out that this API has a number of issues like this.