How to calculate actual font point size in iOS 7 (not the bounding rectangle)?
Asked Answered
T

8

11

Edit: The linked "duplicate" question only deals with calculating text rectangle. I need to calculate actual font size after label scaled it, NOT the string size.

This method is now deprecated:

size = [self sizeWithFont:font // 20
              minFontSize:minFontSize // 14
           actualFontSize:&actualFontSize // 16
                 forWidth:maxWidth
            lineBreakMode:self.lineBreakMode];

How can I calculate font size of a UILabel now in iOS 7 when it shrunk the size of the text to fit in?

Tieshatieup answered 5/10, 2013 at 8:14 Comment(5)
possible duplicate of NSString sizeWithFont: alternative in iOS7Alixaliza
There is no exact replacement according to the docs, you will have to make do with the above method.Alixaliza
borrrden seriously, the other question you refer to is not at all about calculating the actual font size.Tieshatieup
My mistake. If it happens to get closed I'll vote for a reopen.Alixaliza
avoiding from this method is running in the minefield: https://mcmap.net/q/153229/-how-to-make-nsstringdrawingcontext-shrink-text/751932Assuan
A
12

I have the same problem, I need to know the actual size to make that the others UILabels in my UIView match.

I know that it's not a perfect solution, but perhaps it's useful for you.

My solution is: instead of use adjustsFontSizeToFitWidth I calculate "manually" the size.

CGSize initialSize = [_label.text sizeWithAttributes:@{NSFontAttributeName:_label.font}];
while ( initialSize.width > _label.frame.size.width ) {
    [_label setFont:[_label.font fontWithSize:_label.font.pointSize - 1]];
    initialSize = [_label.text sizeWithAttributes:@{NSFontAttributeName:_label.font}];
}
CGFloat actualSize = _label.font.pointSize;
Alberta answered 8/11, 2013 at 10:56 Comment(0)
N
9

Distilled from Julius Bahr's answer on this page, this method works perfectly for getting the actual font size after it has been automatically adjusted:

- (CGFloat)getActualFontSizeForLabel:(UILabel *)label
{
    NSStringDrawingContext *labelContext = [NSStringDrawingContext new];
    labelContext.minimumScaleFactor = label.minimumScaleFactor;

    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:label.text attributes:@{ NSFontAttributeName: label.font }];
    [attributedString boundingRectWithSize:label.frame.size
                                   options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
                                   context:labelContext];

    CGFloat actualFontSize = label.font.pointSize * labelContext.actualScaleFactor;
    return actualFontSize;
}

I am using this in my application to get the font sizes for three different labels for which I need to keep the sizes in synch while still allowing them to auto-shrink for localized translations that can be quite a bit longer than their original English counterparts.

I call that method once for each label, and then if they are not all the same value, I set the label's font sizes to the minimum of the three.

Nazar answered 6/6, 2015 at 2:41 Comment(2)
Whereabouts would you call this? Using it in layoutSubviews here returns the default font size rather than the adjusted one.Memorabilia
richsage, in all cases I am calling this at the end of my View Controllers' -viewWillLayoutSubviews methods, and it's working perfectly for me.Nazar
E
8

The use of minFontSize was deprecated on UILabel in iOS 6, and on the NSString drawing additions in iOS 7. If you want to use it and find the actual font size used, you need to use the deprecated method you mentioned in your question.

The replacement for minFontSize is minimumScaleFactor. If you want to find the actual scale factor used, you need to create an NSStringDrawingContext and pass it in the boundingRectWithSize:options:attributes:context: message, like this:

NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
context.minimumScaleFactor = 0.7;
[label.text boundingRectWithSize:CGSizeMake(maxWidth, HUGE_VAL)
    options:NSStringDrawingUsesLineFragmentOrigin
    attributes:@{
        NSFontAttributeName: font
    } context:context];
CGFloat actualFontSize = font.pointSize * context.actualScaleFactor;
Evasive answered 5/10, 2013 at 17:0 Comment(4)
actualFontSize is always the same on 7.0.3. It's simply broken.Statuette
it doesn't change for me as well. It's broken :(Aniakudo
The NSString version is broken. You need to use NSAttributedString's equivalent method. -[NSAttributedString boundingRectWithSize:options:context]Massproduce
i can't confirm it works properly. For NSAttributedString size with result font bigger than maxWidth :( use next way to calc actualFont: https://mcmap.net/q/153225/-how-to-calculate-actual-font-point-size-in-ios-7-not-the-bounding-rectangleAssuan
G
5

Expanding on Ferran's answer

To expand to fill width or height, whichever it hits first

Swift version

func getFontSizeToFitFrameOfLabel(label: UILabel) -> CGFloat
{
    var initialSize : CGSize = label.text!.sizeWithAttributes([NSFontAttributeName : label.font])

    if initialSize.width > label.frame.size.width ||
       initialSize.height > label.frame.size.height
    {
        while initialSize.width > label.frame.size.width ||
              initialSize.height > label.frame.size.height
        {
            label.font = label.font.fontWithSize(label.font.pointSize - 1)
            initialSize = label.text!.sizeWithAttributes([NSFontAttributeName : label.font])
        }
    } else {
        while initialSize.width < label.frame.size.width &&
              initialSize.height < label.frame.size.height
        {
            label.font = label.font.fontWithSize(label.font.pointSize + 1)
            initialSize = label.text!.sizeWithAttributes([NSFontAttributeName : label.font])
        }
        // went 1 point too large so compensate here
        label.font = label.font.fontWithSize(label.font.pointSize - 1)
    }
    return label.font.pointSize;
}

Then do something like this to use it (say your label is named title1Label)

 title1Label.frame = CGRect(x: 0.0, y: 0.0, width: view.frame.size.width, height: view.frame.size.height)
 // sets font to some nonzero size to begin with, it will change up or down to fit the label's frame
 title1Label.font = UIFont(name: "Super Mario 256", size: 45.0)
 title1Label.font = title1Label.font.fontWithSize(getFontSizeToFitFrameOfLabel(title1Label))
 // resize height to be a little larger than the font height
 title1Label.frame.size.height = title1Label.font.pointSize*1.3

Objective C version:

- (CGFloat) maxFontSize:(UILabel *)label{
   CGSize initialSize = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];

   if (initialSize.width > label.frame.size.width ||
       initialSize.height > label.frame.size.height)
   {
       while (initialSize.width > label.frame.size.width ||
              initialSize.height > label.frame.size.height)
       {
           [label setFont:[label.font fontWithSize:label.font.pointSize - 1]];
           initialSize = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
       }
   } else {
       while (initialSize.width < label.frame.size.width &&
              initialSize.height < label.frame.size.height)
       {
           [label setFont:[label.font fontWithSize:label.font.pointSize + 1]];
           initialSize = [label.text sizeWithAttributes:@{NSFontAttributeName:label.font}];
       }
       // went 1 point too large so compensate here
       [label setFont:[label.font fontWithSize:label.font.pointSize - 1]];
   }
   return label.font.pointSize;
}
Graehme answered 26/6, 2015 at 1:14 Comment(1)
After 1.5 hrs of searching this is something that actually works when it comes to getting actual point size. ThanksGouveia
L
4

My specific quest has been to size the font on 2 labels equally with adjustsFontSizeToFitWidth enabled. The solution works on iOS 6 and 7.

+ (void)sizeLabelFontToMinSizeFor:(UILabel *)label1 and:(UILabel *)label2 {

NSStringDrawingContext *labelContext = [NSStringDrawingContext new];
labelContext.minimumScaleFactor = label1.minimumScaleFactor;

NSAttributedString *attributedString1 = [[NSAttributedString alloc] initWithString:label1.text attributes:@{NSFontAttributeName : label1.font}];
// the NSStringDrawingUsesLineFragmentOrigin and NSStringDrawingUsesFontLeading options are magic
[attributedString1 boundingRectWithSize:label1.frame.size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:labelContext];

CGFloat actualFontSize1 = label1.font.pointSize * labelContext.actualScaleFactor;

labelContext = [NSStringDrawingContext new];
labelContext.minimumScaleFactor = label2.minimumScaleFactor;

NSAttributedString *attributedString2 = [[NSAttributedString alloc] initWithString:label2.text attributes:@{NSFontAttributeName : label2.font}];
[attributedString2 boundingRectWithSize:label2.frame.size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:labelContext];

CGFloat actualFontSize2 = label2.font.pointSize * labelContext.actualScaleFactor;

CGFloat minSize = MIN(actualFontSize1, actualFontSize2);

label1.font = [UIFont fontWithName:RCDefaultFontName size:minSize];
label2.font = [UIFont fontWithName:RCDefaultFontName size:minSize];
}
Logographic answered 25/2, 2014 at 15:13 Comment(0)
A
2

Next code doesn't support minFontSize and lineBreakMode so if you need them you should improve it by yourself:

CGSize NSString_sizeWithFont(NSString * str, UIFont *font) {
    CGSize result;
    if (NO == [str respondsToSelector: @selector(sizeWithAttributes:)]) {
        // legacy way
        result = [str sizeWithFont: font];
    } else {
        // modern way
        NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
                               font, NSFontAttributeName, nil];
        result = [str sizeWithAttributes: dict];
    }
    return result;
}

UIFont * NSString_calcActualFont(NSString * str, UIFont * initialFont,
                                 CGSize sizeLimit, CGSize * actualSize)
{
    const CGSize curSize = NSString_sizeWithFont(str, initialFont);

    CGFloat actualFontSize = initialFont.pointSize;
    actualFontSize *= MIN(sizeLimit.width / curSize.width, sizeLimit.height / curSize.height);

    UIFont * actualFont = [initialFont fontWithSize: floorf(actualFontSize)];
    *actualSize = NSString_sizeWithFont(str, actualFont);

    return actualFont;
}
Assuan answered 20/10, 2014 at 7:24 Comment(0)
S
0

Simple solution for one-line UILabel:

//myLabel - initial label

UILabel *fullSizeLabel = [UILabel new];
fullSizeLabel.font = myLabel.font;
fullSizeLabel.text = myLabel.text;
[fullSizeLabel sizeToFit];

CGFloat actualFontSize = myLabel.font.pointSize * (myLabel.bounds.size.width / fullSizeLabel.bounds.size.width);

//correct, if new font size bigger than initial
actualFontSize = actualFontSize < myLabel.font.pointSize ? actualFontSize : myLabel.font.pointSize;
Streetman answered 12/11, 2016 at 1:14 Comment(0)
A
-1

Erik van der Neut's code worked for me, so I translated it in Swift and wrapped it in a UILabel extension:

extension UILabel {

    public func actualFontSize()-> CGFloat {
        let context = NSStringDrawingContext()
        context.minimumScaleFactor = self.minimumScaleFactor

        let attributedString = NSAttributedString(string: self.text ?? "", attributes: [NSFontAttributeName: self.font])
        attributedString.boundingRectWithSize(self.frame.size, options: [.UsesLineFragmentOrigin], context: context)

        return (self.font.pointSize * context.actualScaleFactor)
    }
}
Abiogenetic answered 28/1, 2016 at 18:59 Comment(1)
options should include .UsesFontLeading.Waggery

© 2022 - 2024 — McMap. All rights reserved.