Vertically Align UILabel text with constraints and no wrap (auto layout, single line)

前端 未结 4 1633
深忆病人
深忆病人 2021-02-05 13:19

So I have my view setup in IB such that this text label aligns with the top of the thumbnail via constraints.

\"ente

4条回答
  •  孤城傲影
    2021-02-05 13:24

    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.

提交回复
热议问题