iOS之UILabel行高行距知多少

設(shè)計(jì)圖的Label和iOS中的Lable

skech中打開設(shè)計(jì)師畫的Lable,按照標(biāo)注寫好后坞嘀,對(duì)比總是發(fā)現(xiàn)和設(shè)計(jì)稿有差異躯护,每次設(shè)計(jì)師說,讓『把行距調(diào)大一點(diǎn)點(diǎn)』加xx幾個(gè)像素等等之類丽涩,都特么頭疼棺滞,因?yàn)樵?iOS 這個(gè)對(duì)文字處理各種不友好的系統(tǒng)里,改行距并不像改字號(hào)那么簡單矢渊,只調(diào)『一點(diǎn)點(diǎn)』未必像我們想的那樣继准,改一個(gè)你想當(dāng)然數(shù)字。

文字在iOS中應(yīng)用最廣的就是UILabel矮男。UILabel里文字的高度并不是UILabel本身的高度移必。例如一個(gè) UILabel 字號(hào)為14,有些程序員可能就會(huì)把這個(gè) Label 高度定為 14 像素了昂灵。而經(jīng)驗(yàn)豐富的人就會(huì)知道不能這樣,否則『g』之類的字母都可能會(huì)被切掉一些舞萄。在 xib 里眨补,選中 label 之后按『Command + =』會(huì)發(fā)現(xiàn)字號(hào)為 14 的 label 合適的高度應(yīng)該是 比14大。那我們怎么友好的將設(shè)計(jì)圖上的標(biāo)注準(zhǔn)確的應(yīng)用到iOS的系統(tǒng)中呢倒脓?

字體的高度(lineHeight)

一個(gè)字形由很多參數(shù)構(gòu)成撑螺。字形的各個(gè)參數(shù),如下面的兩張圖



邊框(Bounding Box):一個(gè)假想的邊框崎弃,盡可能地容納整個(gè)字形甘晤。
基線(Baseline):一條假想的參照線含潘,以此為基礎(chǔ)進(jìn)行字形的渲染。一般來說是一條橫線线婚。
基礎(chǔ)原點(diǎn)(Origin):基線上最左側(cè)的點(diǎn)遏弱。
行間距(Leading):行與行之間的間距。
字間距(Kerning):字與字之間的距離塞弊,為了排版的美觀漱逸,并不是所有的字形之間的距離都是一致的,但是這個(gè)基本步影響到我們的文字排版游沿。
上行高度(Ascent)和下行高度(Decent):一個(gè)字形最高點(diǎn)和最低點(diǎn)到基線的距離饰抒,前者為正數(shù),而后者為負(fù)數(shù)诀黍。當(dāng)同一行內(nèi)有不同字體的文字時(shí)袋坑,就取最大值作為相應(yīng)的值。如下圖:

紅框高度既為當(dāng)前行的行高眯勾,綠線為baseline枣宫,綠色到紅框上部分為當(dāng)前行的最大Ascent,綠線到黃線為當(dāng)前行的最大Desent咒精,而黃框的高即為行間距镶柱。

由此可以得出:lineHeight = Ascent + |Decent| + Leading。lineHeight就是我們字體的真是高度模叙,不同的字體庫lineHeight會(huì)有差異歇拆。
pointSize就是我們字體的字號(hào)。顯而易見個(gè) lineHeight > pointSize范咨。所以一個(gè)單行的14號(hào)字體的高度并不是pointSize的14,而是lineHeight故觅。如果我們直接設(shè)置空間label高度14,有些字就會(huì)被截?cái)唷?/p>

這些字形參數(shù)iOS系統(tǒng)的UIFont都有對(duì)應(yīng)的屬性渠啊,如下:

@property(nonatomic,readonly,strong) NSString *familyName;
@property(nonatomic,readonly,strong) NSString *fontName;
@property(nonatomic,readonly)        CGFloat   pointSize;
@property(nonatomic,readonly)        CGFloat   ascender;
@property(nonatomic,readonly)        CGFloat   descender;
@property(nonatomic,readonly)        CGFloat   capHeight;
@property(nonatomic,readonly)        CGFloat   xHeight;
@property(nonatomic,readonly)        CGFloat   lineHeight NS_AVAILABLE_IOS(4_0);
@property(nonatomic,readonly)        CGFloat   leading;

這里的lineHeight就是這個(gè)字體單行的高度输吏,我們可以用這個(gè)值來判斷文本是單行還是多行。這里提供一個(gè)計(jì)算文本高度的方法:

@implementation NSString (Size)
- (CGSize)sizeWithFont:(UIFont *)font boundSize:(CGSize)size lineSpacing:(CGFloat)lineSpacing{
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];
    paragraphStyle.lineSpacing = lineSpacing;
    //設(shè)置換行模式為單詞模式
    paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
    NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle};
    CGSize resultSize = [self boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine attributes:attributes context:nil].size;
    return CGSizeMake(ceil(resultSize.width), ceil(resultSize.height));
}
@end

字體行間距

當(dāng)字體多行的時(shí)候替蛉,為了美觀贯溅,設(shè)計(jì)師會(huì)寫上行間距,使用attributedString的時(shí)候我們可以設(shè)置行間距躲查。那么行間距在視圖上到底是那一塊呢它浅?如下圖所示:


行間距示意圖

由圖所示,視覺上的行距其實(shí)由那 3 部分組成:上面一行的默認(rèn)空白 + 行距 + 下面一行的默認(rèn)空白镣煮。綠色高度是我們寫的 lineSpacing姐霍,而兩段紅色加起來正好是一倍font.lineHeight - font.pointSize的值。

@implementation NSAttributedString (Size)
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string  font:(UIFont *)font color:(UIColor *)color lineSpacing:(CGFloat)spacing {  
 if (!font) {
        NSAssert(0, @"請(qǐng)傳遞一個(gè)正常的字體參數(shù)");
    }
    
    if (!color) {
        NSAssert(0, @"請(qǐng)傳遞一個(gè)正常的字體參數(shù)");
    }
    
    if (![string isNonEmpty]) {
        return [[NSMutableAttributedString alloc] initWithString:@"" attributes:nil];;
    }
    NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
    style.lineSpacing = spacing;
    style.lineBreakMode = NSLineBreakByWordWrapping;
    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:@{NSFontAttributeName:font, NSForegroundColorAttributeName:color, NSParagraphStyleAttributeName:style}];
    return [attributedString mutableCopy];
}
@end

- (BOOL)isNonEmpty {
    
    NSMutableCharacterSet *emptyStringSet = [[NSMutableCharacterSet alloc] init];
    [emptyStringSet formUnionWithCharacterSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
    [emptyStringSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @" "]];
    if ([self length] == 0) {
        return NO;
    }
    NSString* str = [self stringByTrimmingCharactersInSet:emptyStringSet];
    return [str length] > 0;
}

同樣這里我提供了一個(gè)可以傳入行間距合成attributedString的方法,前面的nil檢查很有必要加镊折。因?yàn)閇[NSMutableAttributedString alloc] initWithString:text] 不接受 nil 參數(shù)胯府,會(huì)直接 crash。isNonEmpty是一個(gè)用來檢測字符串是否為空的方法恨胚,具體實(shí)現(xiàn)如下骂因。通過一個(gè)NSString得到NSMutableAttributedString后,直接賦值給UILable的attributedString屬性与纽。這樣我們就可以方便的設(shè)置文本的間距了侣签,是不是覺得很完美。

受蘋果歧視的中文行間距

我們先看一段的代碼急迂,如下:

    NSString * text1 = @"hhh小書包影所!";//@"hello world!";
    UILabel *label1 = [[UILabel alloc]initWithFrame:CGRectMake(50, 100, 150, 0)];
    label1.text = text1;
    label1.font = font;
    label1.numberOfLines = 0;
    label1.backgroundColor = UIColor.redColor;
    [self.view addSubview:label1];
    [label1 sizeToFit];
    NSLog(@"label1.heihgt = %2f",label1.bounds.size.height);
    NSLog(@"label1.lineHeight = %2f",label1.font.lineHeight);
    
    
    NSString * text2 = @"hhh小書包!小書包僚碎!小書包猴娩!小書包!小書包勺阐!小書包卷中!小書包!小書包渊抽!";//@"hello world!";
    UILabel *label2 = [[UILabel alloc]initWithFrame:CGRectMake(200, 100, 150, 0)];
    label2.text = text2;
    label2.font = font;
    label2.numberOfLines = 0;
    label2.backgroundColor = UIColor.redColor;
    [self.view addSubview:label2];
    [label2 sizeToFit];
    NSLog(@"label1.heihgt = %2f",label1.bounds.size.height);
    NSLog(@"label1.lineHeight = %2f",label1.font.lineHeight);
    
    NSString * text3 = @"hhh小書包蟆豫!";//@"hello world!";
    UILabel *label3 = [[UILabel alloc]initWithFrame:CGRectMake(50, 250, 150, 0)];
    NSAttributedString * attriString3 = [NSAttributedString attributedStringWithString:text3 font:font color:UIColor.blackColor lineSpacing:5];
    label3.attributedText = attriString3;
    label3.numberOfLines = 0;
    label3.backgroundColor = UIColor.redColor;
    [self.view addSubview:label3];
    [label3 sizeToFit];
    NSLog(@"label2.heihgt = %2f",label2.bounds.size.height);
    NSLog(@"label2.lineHeight = %2f",label2.font.lineHeight);
    
    
    NSString * text4 = @"hhh小書包!小書包懒闷!小書包十减!";//@"hello world!";
    UILabel *label4 = [[UILabel alloc]initWithFrame:CGRectMake(200, 250, 150, 0)];
    NSAttributedString * attriString4 = [NSAttributedString attributedStringWithString:text4 font:font color:UIColor.blackColor lineSpacing:5];
    label4.attributedText = attriString4;
    label4.numberOfLines = 0;
    label4.backgroundColor = UIColor.redColor;
    [self.view addSubview:label4];
    [label4 sizeToFit];
    
    NSLog(@"label3.heihgt = %2f",label3.bounds.size.height);
    NSLog(@"label3.lineHeight = %2f",label3.font.lineHeight);

很簡單對(duì)不對(duì),但是接下來看看我們屏幕上的UI顯示:
中英混合內(nèi)容

中英文混合

上面label中的內(nèi)容是中英文混合愤估,那如果文本內(nèi)容是純中文或者純英文呢(其他國家文字暫不考慮)帮辟,我們改下文字內(nèi)容看看最后效果,效果圖分別如下:

純英文內(nèi)容

純英文

純中文內(nèi)容

純中文

從圖中可以看出很明顯感覺到蘋果對(duì)中文深深的惡意玩焰!歸納下來就是這兩點(diǎn):

  • 當(dāng)內(nèi)容是英文的時(shí)候由驹,無論單行還是多行,使用attributedString行高都不會(huì)出現(xiàn)問題昔园。
  • 當(dāng)內(nèi)容中包含的中文的時(shí)候蔓榄,單行的attributedString會(huì)自動(dòng)加上一個(gè)多余的行間距,多行則顯示正常默刚。

所以我們在使用attributedString就得單獨(dú)處理一行的情況甥郑,去掉這個(gè)多余的行間距l(xiāng)inespace。我們在NSAttributedString +Size 加一個(gè)類別方法

+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string  font:(UIFont *)font color:(UIColor *)color maxWidth:(CGFloat) maxWidth lineSpacing:(CGFloat)spacing {
    if (!font) {
        NSAssert(0, @"請(qǐng)傳遞一個(gè)正常的字體參數(shù)");
    }
    
    if (!color) {
        NSAssert(0, @"請(qǐng)傳遞一個(gè)正常的字體參數(shù)");
    }
    
    if (![string isNonEmpty]) {
        return [[NSMutableAttributedString alloc] initWithString:@"" attributes:nil];;
    }
    NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
    style.lineBreakMode = NSLineBreakByWordWrapping;
    NSDictionary * attributes = @{NSFontAttributeName:font, NSForegroundColorAttributeName:color, NSParagraphStyleAttributeName:style};
    CGFloat contentHeight = [string boundingRectWithSize:CGSizeMake(maxWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine attributes:attributes context:nil].size.height;
    if (contentHeight > font.lineHeight){
        style.lineSpacing = spacing;
    } else {
        //單行的時(shí)候去掉行間距
        style.lineSpacing = 0;
    }
    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
    return [attributedString mutableCopy];
}

思考:
我們定義UILable使用sizeToFit自動(dòng)撐開的高度為labelHeight,
lableHeight 和 lineHeight ,lineSpace應(yīng)該有如下關(guān)系:
lableHeight = lineHeight * numberOfLines + lineSpace * (numberOfLines - 1)
但是經(jīng)過測試log打印結(jié)果發(fā)現(xiàn)
lableHeight > lineHeight * numberOfLines + lineSpace * (numberOfLines - 1)
舉個(gè)栗子:

    UIFont *font = [UIFont systemFontOfSize:24];
    NSString * text4 = @"hello world!hello world!hello world!";//@"hello world!";
    UILabel *label4 = [[UILabel alloc]initWithFrame:CGRectMake(200, 250, 150, 0)];
    NSAttributedString * attriString4 = [NSAttributedString attributedStringWithString:text4 font:font color:UIColor.blackColor maxWidth:150 lineSpacing:5];
    label4.attributedText = attriString4;
    label4.adjustsFontSizeToFitWidth = YES;
    label4.minimumScaleFactor = 0.5;
    label4.numberOfLines = 0;
    label4.backgroundColor = UIColor.redColor;
    [self.view addSubview:label4];
    [label4 sizeToFit];

    NSLog(@"label4.heihgt1 = %2f",label4.bounds.size.height);
    //文本有三行
    NSLog(@"label4.height2 = %2f",label4.font.lineHeight * 3 + 5 * 2);

iPhone8模擬器下log打印結(jié)果

2017-12-18 18:07:13.621879+0800 Color[18188:1273525] label4.heihgt1 = 96.000000
2017-12-18 18:07:13.621999+0800 Color[18188:1273525] label4.height2 = 95.921875

至于這到底是因?yàn)槭裁聪劭茫視簳r(shí)不知道為什么壹若?有知道的銅須請(qǐng)私信我嗅钻,謝謝啦皂冰。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末店展,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子秃流,更是在濱河造成了極大的恐慌赂蕴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舶胀,死亡現(xiàn)場離奇詭異概说,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嚣伐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門糖赔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人轩端,你說我怎么就攤上這事放典。” “怎么了基茵?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵奋构,是天一觀的道長。 經(jīng)常有香客問我拱层,道長弥臼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任根灯,我火速辦了婚禮径缅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘箱吕。我一直安慰自己芥驳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布茬高。 她就那樣靜靜地躺著兆旬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怎栽。 梳的紋絲不亂的頭發(fā)上丽猬,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音熏瞄,去河邊找鬼脚祟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛强饮,可吹牛的內(nèi)容都是我干的由桌。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼行您!你這毒婦竟也來了铭乾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤娃循,失蹤者是張志新(化名)和其女友劉穎炕檩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捌斧,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笛质,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捞蚂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妇押。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖姓迅,靈堂內(nèi)的尸體忽然破棺而出舆吮,到底是詐尸還是另有隱情,我是刑警寧澤队贱,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布色冀,位于F島的核電站,受9級(jí)特大地震影響柱嫌,放射性物質(zhì)發(fā)生泄漏锋恬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一编丘、第九天 我趴在偏房一處隱蔽的房頂上張望与学。 院中可真熱鬧,春花似錦嘉抓、人聲如沸索守。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卵佛。三九已至,卻和暖如春敞斋,著一層夾襖步出監(jiān)牢的瞬間截汪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工植捎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衙解,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓焰枢,卻偏偏與公主長得像蚓峦,于是被迫代替她去往敵國和親舌剂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 項(xiàng)目中的要求總是多種多樣的.最近公司項(xiàng)目有新的要求.舉個(gè)例子來說,UIlabel在5s上的字體是16號(hào),正常情況下...
    iOS_小紳士閱讀 1,366評(píng)論 0 5
  • 以前總是很煩設(shè)計(jì)師非要說暑椰,讓『把行距調(diào)大一點(diǎn)點(diǎn)』盐捷,因?yàn)樵?iOS 這個(gè)對(duì)文字處理各種不友好的系統(tǒng)里右冻,改行距并不像改...
    戴倉薯閱讀 24,500評(píng)論 20 188
  • 一故源、 你覺得這里有某種東西疚俱,有某種東西值得去尋找缕碎。其實(shí)乙嘀,在這個(gè)世界上恃疯,你很快就會(huì)明白殴胧。你同樣因?yàn)槭《c世隔絕委造;你...
    stoner_lq閱讀 567評(píng)論 0 0
  • 期盼著星星和夜幕到來的時(shí)候 待在湖邊的自己望著水波的盡頭 我拾起一塊石子往湖里投去 和星光一起沉入水底需要多久 你...
    二明滴滴閱讀 220評(píng)論 0 1
  • 凌晨三點(diǎn)的女人 我的意識(shí)戳鹅,徘徊在酒店房間人為制造的黑暗之中,如同一條不幸被釣到的魚一般昏兆,自我因那安眠藥而沉重不堪枫虏、...
    咔辣辣閱讀 255評(píng)論 0 0