How to adjust font size of label to fit the rectangle?

前端 未结 14 579
独厮守ぢ
独厮守ぢ 2020-11-28 07:00

Yeah, there\'s this cool myLabel.adjustsFontSizeToFitWidth = YES; property. But as soon as the label has two lines or more, it won\'t resize the text to anythin

相关标签:
14条回答
  • 2020-11-28 07:08

    @agarcian's answer was close but it didn't quite work for me, as someone else mentioned in a comment, it always returned 0.

    Here is my attempt.

    Cheers!

    /**
     * Returns the font size required in order to fit the specified text in the specified area.
     * NB! When drawing, be sure to pass in the same options that we pass to boundingRectWithSize:options:attributes:context:
     * Heavily modified form of: http://stackoverflow.com/a/14662750/1027452
     */
    +(NSInteger)fontSizeForText:(NSString *)text withFont:(UIFont *)font inArea:(CGSize)areaSize minFontSize:(NSInteger)minFontSize maxFontSize:(NSInteger)maxFontSize
    {
    // If the sizes are incorrect, return 0, or error, or an assertion.
        if (maxFontSize < minFontSize) {
            return 0;
        }
    
        // Find the middle
        NSInteger fontSize = (minFontSize + maxFontSize) / 2;
        // Create the font
        UIFont *f = [UIFont fontWithName:font.fontName size:fontSize];
        // Create a constraint size with max height
        CGSize constraintSize = CGSizeMake(areaSize.width, MAXFLOAT);
        // Find label size for current font size
        CGRect rect = [text boundingRectWithSize:constraintSize
                                               options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                            attributes:@{NSFontAttributeName : f}
                                               context:nil];
        CGSize labelSize = rect.size;
    
        if (labelSize.height <= areaSize.height && labelSize.width  <= areaSize.width )
        {
            return fontSize;
        }
        else if (labelSize.height > areaSize.height || labelSize.width > areaSize.width)
        {
            return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize maxFontSize:maxFontSize -1];;
        }
        else
        {
            return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize+1 maxFontSize:maxFontSize];;
        }
    }
    
    0 讨论(0)
  • 2020-11-28 07:11

    If someone is looking for a MonoTouch/Xamarin.iOS implementation, as I did ... here you go:

    private int BinarySearchForFontSizeForText(NSString text, int minFontSize, int maxFontSize, SizeF size)
    {
        if (maxFontSize < minFontSize) 
            return minFontSize;
    
        int fontSize = (minFontSize + maxFontSize) / 2;
        UIFont font = UIFont.BoldSystemFontOfSize(fontSize);
    
        var constraintSize = new SizeF(size.Width, float.MaxValue);
        SizeF labelSize = text.StringSize(font, constraintSize, UILineBreakMode.WordWrap);
    
        if (labelSize.Height >= size.Height + 10 && labelSize.Width >= size.Width + 10 && labelSize.Height <= size.Height && labelSize.Width <= size.Width)
            return fontSize;
        else if (labelSize.Height > size.Height || labelSize.Width > size.Width) 
            return BinarySearchForFontSizeForText(text, minFontSize, fontSize - 1, size);
        else 
            return BinarySearchForFontSizeForText(text, fontSize + 1, maxFontSize, size);
    }
    
    private void SizeLabelToRect(UILabel label, RectangleF labelRect)
    {
        label.Frame = labelRect;
    
        int maxFontSize = 300;
        int minFontSize = 5;
        int size = BinarySearchForFontSizeForText(new NSString(label.Text), minFontSize, maxFontSize, label.Frame.Size);
    
        label.Font = UIFont.SystemFontOfSize(size);
    }
    

    It's a translation of agarcian's code from Objective-C to C#, with a small modification: as the returning result has always been 0 (see the comment of borked) I am returning the calculated minFontSize, which results in a correct font size.

    0 讨论(0)
  • 2020-11-28 07:11

    Niels Castle code work find.

    Here is the same idea with a different implementation.
    My solution is more precise but also much more CPU intensive.

    Add this function to a class who inherit UILabel.

    -(void)fitCurrentFrame{
    
        CGSize iHave = self.frame.size;
    
        BOOL isContained = NO;
        do{
            CGSize iWant = [self.text sizeWithFont:self.font];
            if(iWant.width > iHave.width || iWant.height > iHave.height){
                self.font = [UIFont fontWithName:self.font.fontName size:self.font.pointSize - 0.1];
                isContained = NO;
            }else{
                isContained = YES;
            }
    
        }while (isContained == NO);
    }
    
    0 讨论(0)
  • 2020-11-28 07:12

    Since I didn't find a working solution answering to all my needs using the above answers, I have created my own components providing the following features: FittableFontLabel

    • Adjust font to fit height and width when using multilines label
    • Adjust font to fit width when using single line label, the height label will resize itself
    • Support for NSAttributedStrings as well as basic string
    • Auto adjusting the size when changing a label text / frame
    • ...

    If any of you is interesting, it's a full swift library available using CocoaPods: https://github.com/tbaranes/FittableFontLabel

    0 讨论(0)
  • 2020-11-28 07:14

    Here's a Swift extension for UILabel. It runs a binary search algorithm to resize the font and bounds of the label, and is tested to work for iOS 12.

    USAGE: Resizes the font to fit a size of 100x100 (accurate within 1.0 font point) and aligns it to top.

    let adjustedSize = <label>.fitFontForSize(CGSizeMake(100, 100))
    <label>.frame = CGRect(x: 0, y: 0, width: 100, height: adjustedSize.height)
    

    Copy/Paste the following into your file:

    extension UILabel {
        @discardableResult func fitFontForSize(_ constrainedSize: CGSize,
                                               maxFontSize: CGFloat = 100,
                                               minFontSize: CGFloat = 5,
                                               accuracy: CGFloat = 1) -> CGSize {
            assert(maxFontSize > minFontSize)
    
            var minFontSize = minFontSize
            var maxFontSize = maxFontSize
            var fittingSize = constrainedSize
    
            while maxFontSize - minFontSize > accuracy {
                let midFontSize: CGFloat = ((minFontSize + maxFontSize) / 2)
                font = font.withSize(midFontSize)
                fittingSize = sizeThatFits(constrainedSize)
                if fittingSize.height <= constrainedSize.height
                    && fittingSize.width <= constrainedSize.width {
                    minFontSize = midFontSize
                } else {
                    maxFontSize = midFontSize
                }
            }
    
            return fittingSize
        }
    } 
    

    This function will not change the label size, only the font property is affected. You can use the returned size value to adjust the layout of the label.

    0 讨论(0)
  • 2020-11-28 07:16

    I found Niels' answer to be the best answer for this issue. However, I have a UIView that can have 100 labels where I need to fit the text, so this process was very inefficient and I could feel the hit in performance.

    Here is his code modified to use a binary search instead, rather than a linear search. Now it works very efficiently.

    - (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size {
        // If the sizes are incorrect, return 0, or error, or an assertion.
        if (maxFontSize < minFontSize) {
            return 0;
        }
    
        // Find the middle
        NSInteger fontSize = (minFontSize + maxFontSize) / 2;
        // Create the font
        UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize];
        // Create a constraint size with max height
        CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
        // Find label size for current font size
        CGRect rect = [label.text boundingRectWithSize:constraintSize
                                               options:NSStringDrawingUsesLineFragmentOrigin
                                            attributes:@{NSFontAttributeName : font}
                                               context:nil];
        CGSize labelSize = rect.size;
    
        // EDIT:  The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results.
        if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) {
            return fontSize;
        } else if (labelSize.height > size.height || labelSize.width > size.width) {
            return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size];
        } else {
            return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size];
        }
    }
    
    - (void)sizeBinaryLabel:(UILabel *)label toRect:(CGRect)labelRect {
    
        // Set the frame of the label to the targeted rectangle
        label.frame = labelRect;
    
        // Try all font sizes from largest to smallest font
        int maxFontSize = 300;
        int minFontSize = 5;
    
        NSInteger size = [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:maxFontSize withSize:label.frame.size];
    
        label.font = [UIFont fontWithName:label.font.fontName size:size];
    
    }
    

    Credit goes also to https://gist.github.com/988219

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