Multiline UILabel with adjustsFontSizeToFitWidth

前端 未结 8 1467
北海茫月
北海茫月 2020-12-04 12:16

I have a multiline UILabel whose font size I\'d like to adjust depending on the text length. The whole text should fit into the label\'s frame without truncating it.

相关标签:
8条回答
  • 2020-12-04 12:40

    It seems to me that there is now a better answer to this question

    I have discovered that the font size adjusts automatically on a multiline label when you set adjustsFontSizeToFitWidth true, numberOfLines 0, a minimumScaleFactor little enough to let the font shrink as needed, and the default lineBreakMode (byTruncatingTail)

    PS: I cannot find anything about this change on the official documentation. I created a question to find out more informations on this topic here

    0 讨论(0)
  • 2020-12-04 12:46

    In this question, 0x90 provides a solution that - although a bit ugly - does what I want. Specifically, it deals correctly with the situation that a single word does not fit the width at the initial font size. I've slightly modified the code so that it works as a category on NSString:

    - (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
        CGFloat fontSize = [font pointSize];
        CGFloat height = [self sizeWithFont:font constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
        UIFont *newFont = font;
    
        //Reduce font size while too large, break if no height (empty string)
        while (height > size.height && height != 0) {   
            fontSize--;  
            newFont = [UIFont fontWithName:font.fontName size:fontSize];   
            height = [self sizeWithFont:newFont constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
        };
    
        // Loop through words in string and resize to fit
        for (NSString *word in [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
            CGFloat width = [word sizeWithFont:newFont].width;
            while (width > size.width && width != 0) {
                fontSize--;
                newFont = [UIFont fontWithName:font.fontName size:fontSize];   
                width = [word sizeWithFont:newFont].width;
            }
        }
        return fontSize;
    }
    

    To use it with a UILabel:

        CGFloat fontSize = [label.text fontSizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:label.frame.size];
        label.font = [UIFont boldSystemFontOfSize:fontSize];
    

    EDIT: Fixed the code to initialize newFont with font. Fixes a crash under certain circumstances.

    0 讨论(0)
  • 2020-12-04 12:51

    For a fully working solution, see the bottom of my answer

    0 讨论(0)
  • 2020-12-04 12:59

    In some cases, changing "Line Breaks" from "Word Wrap" to "Truncate Tail" may be all you need, if you know how many lines you want (e.g. "2"): Credit: Becky Hansmeyer

    0 讨论(0)
  • 2020-12-04 12:59

    There is an ObjC extension provided in comments, that calculate fontsize required to fit multiline text into UILabel. It was rewritten in Swift (since it is 2016):

    //
    //  NSString+KBAdditions.swift
    //
    //  Created by Alexander Mayatsky on 16/03/16.
    //
    //  Original code from http://stackoverflow.com/a/4383281/463892 & http://stackoverflow.com/a/18951386
    //
    
    import Foundation
    import UIKit
    
    protocol NSStringKBAdditions {
        func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat
    }
    
    extension NSString : NSStringKBAdditions {
        func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat {
            var fontSize = font.pointSize
            let minimumFontSize = fontSize * minimumScaleFactor
    
    
            var attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: font])
            var height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height
    
            var newFont = font
            //Reduce font size while too large, break if no height (empty string)
            while (height > size.height && height != 0 && fontSize > minimumFontSize) {
                fontSize--;
                newFont = UIFont(name: font.fontName, size: fontSize)!
    
                attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: newFont])
                height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height
            }
    
            // Loop through words in string and resize to fit
            for word in self.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) {
                var width = word.sizeWithAttributes([NSFontAttributeName:newFont]).width
                while (width > size.width && width != 0 && fontSize > minimumFontSize) {
                    fontSize--
                    newFont = UIFont(name: font.fontName, size: fontSize)!
                    width = word.sizeWithAttributes([NSFontAttributeName:newFont]).width
                }
            }
            return fontSize;
        }
    }
    

    Link to full code: https://gist.github.com/amayatsky/e6125a2288cc2e4f1bbf

    0 讨论(0)
  • 2020-12-04 13:01

    Thanks, with that and a little more from someone else I did this custom UILabel, that will respect the minimum font size and there's a bonus option to align the text to top.

    h:

    @interface EPCLabel : UILabel {
        float originalPointSize;
        CGSize originalSize;
    }
    
    @property (nonatomic, readwrite) BOOL alignTextOnTop;
    @end
    

    m:

    #import "EPCLabel.h"
    
    @implementation EPCLabel
    @synthesize alignTextOnTop;
    
    -(void)verticalAlignTop {
        CGSize maximumSize = originalSize;
        NSString *dateString = self.text;
        UIFont *dateFont = self.font;
        CGSize dateStringSize = [dateString sizeWithFont:dateFont 
                                       constrainedToSize:CGSizeMake(self.frame.size.width, maximumSize.height)
                                           lineBreakMode:self.lineBreakMode];
    
        CGRect dateFrame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, dateStringSize.height);
    
        self.frame = dateFrame;
    }
    
    - (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
        CGFloat fontSize = [font pointSize];
        CGFloat height = [self.text sizeWithFont:font             
                               constrainedToSize:CGSizeMake(size.width,FLT_MAX)  
                                   lineBreakMode:UILineBreakModeWordWrap].height;
        UIFont *newFont = font;
    
        //Reduce font size while too large, break if no height (empty string)
        while (height > size.height && height != 0 && fontSize > self.minimumFontSize) { 
            fontSize--;  
            newFont = [UIFont fontWithName:font.fontName size:fontSize];   
            height = [self.text sizeWithFont:newFont  
                           constrainedToSize:CGSizeMake(size.width,FLT_MAX) 
                               lineBreakMode:UILineBreakModeWordWrap].height;
        };
    
        // Loop through words in string and resize to fit
        if (fontSize > self.minimumFontSize) {
            for (NSString *word in [self.text componentsSeparatedByString:@" "]) {
                CGFloat width = [word sizeWithFont:newFont].width;
                while (width > size.width && width != 0 && fontSize > self.minimumFontSize) {
                    fontSize--;
                    newFont = [UIFont fontWithName:font.fontName size:fontSize];   
                    width = [word sizeWithFont:newFont].width;
                }
            }
        }
        return fontSize;
    }
    
    -(void)setText:(NSString *)text {
        [super setText:text];
        if (originalSize.height == 0) {
            originalPointSize = self.font.pointSize;
            originalSize = self.frame.size;
        }
    
        if (self.adjustsFontSizeToFitWidth && self.numberOfLines > 1) {
            UIFont *origFont = [UIFont fontWithName:self.font.fontName size:originalPointSize];
            self.font = [UIFont fontWithName:origFont.fontName size:[self fontSizeWithFont:origFont constrainedToSize:originalSize]];
        }
    
        if (self.alignTextOnTop) [self verticalAlignTop];
    }
    
    -(void)setAlignTextOnTop:(BOOL)flag {
        alignTextOnTop = YES;
        if (alignTextOnTop && self.text != nil)
            [self verticalAlignTop];
    }
    
    @end
    

    I hope it helps.

    0 讨论(0)
提交回复
热议问题