設(shè)置行距
UILabel+Utils.m
- (void)setText:(NSString*)text lineSpacing:(CGFloat)lineSpacing {
if (lineSpacing < 0.01 || !text) {
self.text = text;
return;
}
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];
[attributedString addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, [text length])];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:lineSpacing];
[paragraphStyle setLineBreakMode:self.lineBreakMode];
[paragraphStyle setAlignment:self.textAlignment];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [text length])];
self.attributedText = attributedString;
}
使用
[label setText:text lineSpacing:2.0f];
作為一個四處使用的工具方法,前面的nil檢查很有必要加。因為[[NSMutableAttributedString alloc] initWithString:text]不接受 nil 參數(shù),會直接 crash。
生成的 paragraphStyle 除了配行距之外,還帶上了 label 原有的一些常用屬性。如果有其他需要,也可以加在這里。
UITextView+Utils.m
- (void)setText:(NSString*)text lineSpacing:(CGFloat)lineSpacing {
if (lineSpacing < 0.01 || !text) {
self.text = text;
return;
}
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];
[attributedString addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, [text length])];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:lineSpacing];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [attributedText length])];
self.attributedText = attributedString;
}
UITextView 的方法跟 UILabel 基本一樣。
使用
[textView setText:text lineSpacing:2.0f];
計算行高
自定義行距之后,計算文本高度的方法也得相應(yīng)改。很簡單,只要利用 sizeToFit、sizeThatFits 之類的方法就可以了。
UILabel+Utils.m
+ (CGFloat)text:(NSString*)text heightWithFontSize:(CGFloat)fontSize width:(CGFloat)width lineSpacing:(CGFloat)lineSpacing {
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, MAXFLOAT)];
label.font = [UIFont systemFontOfSize:fontSize];
label.numberOfLines = 0;
[label setText:text lineSpacing:lineSpacing];
[label sizeToFit];
return label.height;
}
UITextView+Utils.m
+ (CGFloat)text:(NSString*)text heightWithFontSize:(CGFloat)fontSize width:(CGFloat)width lineSpacing:(CGFloat)lineSpacing {
UITextView* textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, width, MAXFLOAT)];
textView.font = [UIFont systemFontOfSize:fontSize];
[textView setText:text lineSpacing:lineSpacing];
[textView sizeToFit];
return textView.height;
}
因為默認(rèn)的 UITextView 有一點 inset,所以計算文本高度的方法要跟 UILabel 分開。
這幾個方法就能應(yīng)付大多數(shù)需求了。根據(jù)自己需要,我還寫了一些參數(shù)帶有 numberOfLines、文本的參數(shù)為 attributedString 的變體。
代碼上的行距 vs 設(shè)計圖上的行距
如果只為貼上面幾個方法,我可能也就懶得寫這篇文章了。這篇文章的重點其實是分享下面這一點:代碼傳參數(shù)進(jìn)去的行距與設(shè)計圖上量出來的行距是有區(qū)別的,代碼上要少幾個像素,而減少的量跟字體大小有關(guān)。
我感覺這一點有時容易被人忽視。例如一個 UILabel 字號為14,有些程序員可能就會把這個 Label 高度定為 14 像素了。而經(jīng)驗豐富的人就會知道不能這樣,否則『h』『g』之類的字母都可能會被切掉一些。在 xib 里,選中 label 之后按『Command + =』會發(fā)現(xiàn)字號為 14 的 label 合適的高度應(yīng)該是 17。
為了給像『g』、『y』英文字母的尾巴留出空間,系統(tǒng)會給 UILabel 上的文字上下加一點默認(rèn)的空白,這就是 font size 與 line height 的區(qū)別。而用代碼設(shè)定paragraphStyle的lineSpacing,是疊加在原有空白之上的。
別小看這點空白。如果設(shè)計師沒有喪心病狂,設(shè)計出的行距往往也就是 4、5 個像素,而對 14 號字來說上下兩行的空白就能占到 3 像素。如果不假思索地直接把設(shè)計圖的標(biāo)注傳進(jìn)去,結(jié)果就是行距放大到150%。視覺上出了偏差,我們也要負(fù)責(zé)任的。
行距組成示意圖
由圖所示,視覺上的行距其實由那 3 部分組成:上面一行的默認(rèn)空白 + 行距 + 下面一行的默認(rèn)空白。藍(lán)色高度是我們寫的 lineSpacing,而黃色和綠色加起來正好是一倍font.lineHeight - font.pointSize的值(黃色高度是上面一行的一半,為(font.lineHeight - font.pointSize) / 2,綠色是下面一行的一半)。
簡單打下 log 就可以看到這個差值大概是多少。下面列出常見的字號:
font sizefont.lineHeight(近似)差值
為了計算效率高,我們就不在運行時現(xiàn)算這個差值了;直接把設(shè)計圖上量出的行距減去上面這個表里幾個像素的差值,作為參數(shù)傳進(jìn)去即可。例如:14 號字的 label,設(shè)計圖上量出的行距是 5 個像素,那就減去 3 個像素,寫[label setText:text lineSpacing:2.0f];。不要忘了計算行高的時候也要用同樣的參數(shù)~