The core of the response is contained in the Cupcake publication. Anyway, you can use sizeWithFont:constrainedToSize:lineBreakMode: to find out what frame size a particular font will have in a label of a given width, given the specific packaging of words, for example.
CGSize size = [string sizeWithFont:font constrainedToSize:sizeConstraint lineBreakMode:UILineBreakModeWordWrap];
Set sizeConstraint to the same width of your label, but set the height so that it is larger. If the resulting size.height larger than your UILabel, your string is too long. Theoretically, you can delete the last character / word and try again and repeat until it fits.
If you think the lines can be very long, you can go the other way, start with the short part of the line and continue to add characters until they become too large, and then you know the last character.
In any case, this iterative size calculation can be quite intense, so be careful.
Update:
Here is an algorithm that returns the length of an NSString that can fit into the UILabel in question using the default font (but ignoring the 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 it more efficient if, for example, you checked the word break mode, moved to the next word separator, and then called sizeWithFont , but that might be enough for a small UILabel . If you want to use 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]; }
The character set used here may not be entirely correct (for example, if you include hyphens), but it is probably pretty close and much more efficient than character-by-character execution if you don't need to.