Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to know if NSString fits in UILabel or not and index of the last string which fits?

I have 4 lines UILabel with exact frame and font.

I need to know if this string fits the label and what is the index of the last character which fits.

like image 566
B.S. Avatar asked Jun 22 '12 12:06

B.S.


1 Answers

The kernel of the answer is in Cupcake's referenced posting. Anyway, you can use sizeWithFont:constrainedToSize:lineBreakMode: to figure out what the size of a frame would be with a particular font in a label of a given width given a specific word wrapping, e.g.

CGSize size = [string sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:UILineBreakModeWordWrap];

Set sizeConstraint to be the same width of your label, but set the height to be larger. If the resulting size.height is larger than your UILabel, then your string is too long. Theoretically, you could remove the last character/word and try again and repeat until it fits.

If you think the strings might be very long, you might want to go the other way, start with a short portion of the string and keep adding characters until it's too large, and then you know the last character.

Either way, this iterative calculation of the size can be pretty cpu intensive operation, so be careful.

Update:

Here is an algorithm that returns the length of NSString that can fit into the UILabel in question using the default font (but ignoring minimum font size):

- (NSUInteger)fitString:(NSString *)string intoLabel:(UILabel *)label
{
    UIFont *font           = label.font;
    UILineBreakMode mode   = label.lineBreakMode;

    CGFloat labelWidth     = label.frame.size.width;
    CGFloat labelHeight    = label.frame.size.height;
    CGSize  sizeConstraint = CGSizeMake(labelWidth, CGFLOAT_MAX);

    if ([string sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:mode].height > labelHeight) 
    {
        NSString *adjustedString;

        for (NSUInteger i = 1; i < [string length]; i++) 
        {
            adjustedString = [string substringToIndex:i];

            if ([adjustedString sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:mode].height > labelHeight)
                return i - 1;
        }
    }

    return [string length];
}

You could probably make this more efficient if you, for example, checked if word break mode, jumping to the next word separator and then calling sizeWithFont, but for small UILabels this might be sufficient. If you wanted to leverage word-wrap logic to minimize the number of times you call sizeWithFont, you might have something like:

- (NSUInteger)fitString:(NSString *)string intoLabel:(UILabel *)label
{
    UIFont *font           = label.font;
    UILineBreakMode mode   = label.lineBreakMode;

    CGFloat labelWidth     = label.frame.size.width;
    CGFloat labelHeight    = label.frame.size.height;
    CGSize  sizeConstraint = CGSizeMake(labelWidth, CGFLOAT_MAX);

    if ([string sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:mode].height > labelHeight) 
    {
        NSUInteger index = 0;
        NSUInteger prev;
        NSCharacterSet *characterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];

        do 
        {
            prev = index;
            if (mode == UILineBreakModeCharacterWrap)
                index++;
            else
                index = [string rangeOfCharacterFromSet:characterSet options:0 range:NSMakeRange(index + 1, [string length] - index - 1)].location;
        }
        while (index != NSNotFound && index < [string length] && [[string substringToIndex:index] sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:mode].height <= labelHeight);

        return prev;
    }

    return [string length];
}

Perhaps the character set used here isn't quite right (should you include hyphens, for example), but it's probably pretty close and far more efficient than doing character by character, if you don't need to do that.

like image 194
Rob Avatar answered Oct 20 '22 06:10

Rob