问题
I have a layout with a UILabel
placed above a fixed width view, shown below as a grey rectangle.

The text needs to match the width of the fixed grey view.
I achieved this by setting the adjustsFontSizeToFitWidth
property on the UILabel
to YES
, settings the font size to something really large, and setting the minimumScaleFactor
to something suitable small. This worked fine until…
I had to add kerning to said text. I added the kerning by applying @{NSKernAttributeName: @1.40}
to an attributed string and then passed the attributed string UILabel
’s attributedText
property. Unfortunately this seems to stump the automatic scaling, as this results in the text being properly kerned but the end of the string is truncated. It is as though the label scaled the text down without taking kerning into account.

How can I get a given string with kerning to be rendered with a width of my choosing (i.e. the grey view's width)?
回答1:
I am using next code to calculate kerning (by creating NSString extension).
This extension is using quicksort idea of pivot to quickly find kerning that makes the string fit into the needed width.
Please note that kerning less than -3.0 makes ugly characters overlap, so if string is not fitting with kerning = -3, the algorithm just returns -3. Of course you can set bigKern variable to smaller value.
I checked it against UITabBarItem's (Apple uses kerning on tab bar item labels), and my implementation is very similar.
Hope you like it.
@implementation NSString (Extension)
- (CGFloat)kernForFont:(UIFont *)font toFitWidth:(CGFloat)width
{
CGSize size = CGSizeMake(CGFLOAT_MAX, font.pointSize*2); // Size to fit.
const CGFloat threshold = 0.1;
CGFloat bigKern = -3.0, smallKern = 0.0, pivot = 0.0;
NSMutableDictionary *attrs = [NSMutableDictionary new];
attrs[NSFontAttributeName] = font;
while (true) {
attrs[NSKernAttributeName] = @(pivot);
CGRect frame = [self boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attrs
context:nil];
CGFloat diff = width - frame.size.width;
if (diff > -0.5) {
// String is fitting.
if (pivot == 0.0) // Fits without kerning.
return pivot;
else if (smallKern - bigKern <= threshold)
return pivot; // Threshold is reached, return the fitting pivot.
else {
// Pivot is fitting, but threshold is not reached, set pivot as max.
bigKern = pivot;
}
}
else {
// String not fitting.
smallKern = pivot;
if (smallKern - bigKern <= threshold)
return bigKern;
}
pivot = (smallKern + bigKern) / 2.0;
}
return bigKern;
}
@end
Example usage, for custom UITabBarItems:
// I have a tabBarItem of type UITabBarItem. textColor is a UIColor.
NSString *title = tabBarItem.title;
CGFloat textLabelWidth = tabBar.frame.size.width / (CGFloat)(self.tabBar.items.count) - 6.0; // 6 is padding.
UIFont *font = [UIFont systemFontOfSize:10.0];
CGFloat kern = [title kernForFont:font toFitWidth:textLabelWidth];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentCenter;
NSDictionary *attrs = @{
NSFontAttributeName: font,
NSKernAttributeName: @(kern),
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle
};
textLabel.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attrs];
来源:https://stackoverflow.com/questions/25360991/dynamically-sizing-text-with-kerning-to-occupy-the-full-width-of-a-uilabel