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 UILabel
s 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.