CATextLayer使用指南

本文摘自《iOS-Core-Animation-Advanced-Techniques》

CATextLayer

用戶界面是無法從一個(gè)單獨(dú)的圖片里面構(gòu)建的。一個(gè)設(shè)計(jì)良好的圖標(biāo)能夠很好地表現(xiàn)一個(gè)按鈕或控件的意圖枚钓,不過你遲早都要需要一個(gè)不錯(cuò)的老式風(fēng)格的文本標(biāo)簽鹅髓。

如果你想在一個(gè)圖層里面顯示文字,完全可以借助圖層代理直接將字符串使用Core Graphics寫入圖層的內(nèi)容(這就是UILabel的精髓)廓八。如果越過寄宿于圖層的視圖奉芦,直接在圖層上操作,那其實(shí)相當(dāng)繁瑣剧蹂。你要為每一個(gè)顯示文字的圖層創(chuàng)建一個(gè)能像圖層代理一樣工作的類声功,還要邏輯上判斷哪個(gè)圖層需要顯示哪個(gè)字符串,更別提還要記錄不同的字體国夜,顏色等一系列亂七八糟的東西减噪。

萬幸的是這些都是不必要的,Core Animation提供了一個(gè)CALayer的子類CATextLayer,它以圖層的形式包含了UILabel幾乎所有的繪制特性筹裕,并且額外提供了一些新的特性醋闭。

同樣,CATextLayer也要比UILabel渲染得快得多朝卒。很少有人知道在iOS 6及之前的版本证逻,UILabel其實(shí)是通過WebKit來實(shí)現(xiàn)繪制的,這樣就造成了當(dāng)有很多文字的時(shí)候就會有極大的性能壓力抗斤。而CATextLayer使用了Core text囚企,并且渲染得非常快瑞眼。

讓我們來嘗試用CATextLayer來顯示一些文字龙宏。清單6.2的代碼實(shí)現(xiàn)了這一功能,結(jié)果如圖6.2所示伤疙。

清單6.2 用CATextLayer來實(shí)現(xiàn)一個(gè)UILabel

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *labelView;

@end

@implementation ViewController
- (void)viewDidLoad
{
  [super viewDidLoad];

  //create a text layer
  CATextLayer *textLayer = [CATextLayer layer];
  textLayer.frame = self.labelView.bounds;
  [self.labelView.layer addSublayer:textLayer];

  //set text attributes
  textLayer.foregroundColor = [UIColor blackColor].CGColor;
  textLayer.alignmentMode = kCAAlignmentJustified;
  textLayer.wrapped = YES;

  //choose a font
  UIFont *font = [UIFont systemFontOfSize:15];

  //set layer font
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  textLayer.font = fontRef;
  textLayer.fontSize = font.pointSize;
  CGFontRelease(fontRef);

  //choose some text
  NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";

  //set layer text
  textLayer.string = text;
}
@end
圖6.2

圖6.2 用CATextLayer來顯示一個(gè)純文本標(biāo)簽

如果你仔細(xì)看這個(gè)文本银酗,你會發(fā)現(xiàn)一個(gè)奇怪的地方:這些文本有一些像素化了。這是因?yàn)椴]有以Retina的方式渲染徒像,第二章提到了這個(gè)contentScale屬性黍特,用來決定圖層內(nèi)容應(yīng)該以怎樣的分辨率來渲染。contentsScale并不關(guān)心屏幕的拉伸因素而總是默認(rèn)為1.0锯蛀。如果我們想以Retina的質(zhì)量來顯示文字灭衷,我們就得手動地設(shè)置CATextLayercontentsScale屬性,如下:

textLayer.contentsScale = [UIScreen mainScreen].scale;

這樣就解決了這個(gè)問題(如圖6.3)

圖6.3

圖6.3 設(shè)置contentsScale來匹配屏幕

CATextLayerfont屬性不是一個(gè)UIFont類型旁涤,而是一個(gè)CFTypeRef類型翔曲。這樣可以根據(jù)你的具體需要來決定字體屬性應(yīng)該是用CGFontRef類型還是CTFontRef類型(Core Text字體)。同時(shí)字體大小也是用fontSize屬性單獨(dú)設(shè)置的拭抬,因?yàn)?code>CTFontRef和CGFontRef并不像UIFont一樣包含點(diǎn)大小部默。這個(gè)例子會告訴你如何將UIFont轉(zhuǎn)換成CGFontRef

另外造虎,CATextLayerstring屬性并不是你想象的NSString類型傅蹂,而是id類型。這樣你既可以用NSString也可以用NSAttributedString來指定文本了(注意算凿,NSAttributedString并不是NSString的子類)份蝴。屬性化字符串是iOS用來渲染字體風(fēng)格的機(jī)制,它以特定的方式來決定指定范圍內(nèi)的字符串的原始信息氓轰,比如字體婚夫,顏色,字重署鸡,斜體等案糙。

富文本

iOS 6中限嫌,Apple給UILabel和其他UIKit文本視圖添加了直接的屬性化字符串的支持,應(yīng)該說這是一個(gè)很方便的特性时捌。不過事實(shí)上從iOS3.2開始CATextLayer就已經(jīng)支持屬性化字符串了怒医。這樣的話,如果你想要支持更低版本的iOS系統(tǒng)奢讨,CATextLayer無疑是你向界面中增加富文本的好辦法稚叹,而且也不用去跟復(fù)雜的Core Text打交道,也省了用UIWebView的麻煩拿诸。

讓我們編輯一下示例使用到NSAttributedString(見清單6.3).iOS 6及以上我們可以用新的NSTextAttributeName實(shí)例來設(shè)置我們的字符串屬性扒袖,但是練習(xí)的目的是為了演示在iOS 5及以下,所以我們用了Core Text亩码,也就是說你需要把Core Text framework添加到你的項(xiàng)目中季率。否則,編譯器是無法識別屬性常量的描沟。

圖6.4是代碼運(yùn)行結(jié)果(注意那個(gè)紅色的下劃線文本)

清單6.3 用NSAttributedString實(shí)現(xiàn)一個(gè)富文本標(biāo)簽蚀同。

#import "DrawingView.h"
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *labelView;

@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  //create a text layer
  CATextLayer *textLayer = [CATextLayer layer];
  textLayer.frame = self.labelView.bounds;
  textLayer.contentsScale = [UIScreen mainScreen].scale;
  [self.labelView.layer addSublayer:textLayer];

  //set text attributes
  textLayer.alignmentMode = kCAAlignmentJustified;
  textLayer.wrapped = YES;

  //choose a font
  UIFont *font = [UIFont systemFontOfSize:15];

  //choose some text
  NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc \ elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";
  
  //create attributed string
  NSMutableAttributedString *string = nil;
  string = [[NSMutableAttributedString alloc] initWithString:text];

  //convert UIFont to a CTFont
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFloat fontSize = font.pointSize;
  CTFontRef fontRef = CTFontCreateWithName(fontName, fontSize, NULL);

  //set text attributes
  NSDictionary *attribs = @{
    (__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor blackColor].CGColor,
    (__bridge id)kCTFontAttributeName: (__bridge id)fontRef
  };

  [string setAttributes:attribs range:NSMakeRange(0, [text length])];
  attribs = @{
    (__bridge id)kCTForegroundColorAttributeName: (__bridge id)[UIColor redColor].CGColor,
    (__bridge id)kCTUnderlineStyleAttributeName: @(kCTUnderlineStyleSingle),
    (__bridge id)kCTFontAttributeName: (__bridge id)fontRef
  };
  [string setAttributes:attribs range:NSMakeRange(6, 5)];

  //release the CTFont we created earlier
  CFRelease(fontRef);

  //set layer text
  textLayer.string = string;
}
@end
圖6.4

圖6.4 用CATextLayer實(shí)現(xiàn)一個(gè)富文本標(biāo)簽。

行距和字距

有必要提一下的是啊掏,由于繪制的實(shí)現(xiàn)機(jī)制不同(Core Text和WebKit),用CATextLayer渲染和用UILabel渲染出的文本行距和字距也不是不盡相同的衰猛。

二者的差異程度(由使用的字體和字符決定)總的來說挺小迟蜜,但是如果你想正確的顯示普通便簽和CATextLayer就一定要記住這一點(diǎn)。

UILabel的替代品

我們已經(jīng)證實(shí)了CATextLayerUILabel有著更好的性能表現(xiàn)啡省,同時(shí)還有額外的布局選項(xiàng)并且在iOS 5上支持富文本娜睛。但是與一般的標(biāo)簽比較而言會更加繁瑣一些。如果我們真的在需求一個(gè)UILabel的可用替代品卦睹,最好是能夠在Interface Builder上創(chuàng)建我們的標(biāo)簽畦戒,而且盡可能地像一般的視圖一樣正常工作。

我們應(yīng)該繼承UILabel结序,然后添加一個(gè)子圖層CATextLayer并重寫顯示文本的方法障斋。但是仍然會有由UILabel-drawRect:方法創(chuàng)建的空寄宿圖。而且由于CALayer不支持自動縮放和自動布局徐鹤,子視圖并不是主動跟蹤視圖邊界的大小垃环,所以每次視圖大小被更改,我們不得不手動更新子圖層的邊界返敬。

我們真正想要的是一個(gè)用CATextLayer作為宿主圖層的UILabel子類遂庄,這樣就可以隨著視圖自動調(diào)整大小而且也沒有冗余的寄宿圖啦。

就像我們在第一章『圖層樹』討論的一樣劲赠,每一個(gè)UIView都是寄宿在一個(gè)CALayer的示例上涛目。這個(gè)圖層是由視圖自動創(chuàng)建和管理的秸谢,那我們可以用別的圖層類型替代它么?一旦被創(chuàng)建霹肝,我們就無法代替這個(gè)圖層了估蹄。但是如果我們繼承了UIView,那我們就可以重寫+layerClass方法使得在創(chuàng)建的時(shí)候能返回一個(gè)不同的圖層子類阿迈。UIView會在初始化的時(shí)候調(diào)用+layerClass方法元媚,然后用它的返回類型來創(chuàng)建宿主圖層。

清單6.4 演示了一個(gè)UILabel子類LayerLabelCATextLayer繪制它的問題苗沧,而不是調(diào)用一般的UILabel使用的較慢的-drawRect:方法刊棕。LayerLabel示例既可以用代碼實(shí)現(xiàn),也可以在Interface Builder實(shí)現(xiàn)待逞,只要把普通的標(biāo)簽拖入視圖之中甥角,然后設(shè)置它的類是LayerLabel就可以了。

清單6.4 使用CATextLayerUILabel子類:LayerLabel

#import "LayerLabel.h"
#import <QuartzCore/QuartzCore.h>

@implementation LayerLabel
+ (Class)layerClass
{
  //this makes our label create a CATextLayer //instead of a regular CALayer for its backing layer
  return [CATextLayer class];
}

- (CATextLayer *)textLayer
{
  return (CATextLayer *)self.layer;
}

- (void)setUp
{
  //set defaults from UILabel settings
  self.text = self.text;
  self.textColor = self.textColor;
  self.font = self.font;

  //we should really derive these from the UILabel settings too
  //but that's complicated, so for now we'll just hard-code them
  [self textLayer].alignmentMode = kCAAlignmentJustified;
  
  [self textLayer].wrapped = YES;
  [self.layer display];
}

- (id)initWithFrame:(CGRect)frame
{
  //called when creating label programmatically
  if (self = [super initWithFrame:frame]) {
    [self setUp];
  }
  return self;
}

- (void)awakeFromNib
{
  //called when creating label using Interface Builder
  [self setUp];
}

- (void)setText:(NSString *)text
{
  super.text = text;
  //set layer text
  [self textLayer].string = text;
}

- (void)setTextColor:(UIColor *)textColor
{
  super.textColor = textColor;
  //set layer text color
  [self textLayer].foregroundColor = textColor.CGColor;
}

- (void)setFont:(UIFont *)font
{
  super.font = font;
  //set layer font
  CFStringRef fontName = (__bridge CFStringRef)font.fontName;
  CGFontRef fontRef = CGFontCreateWithFontName(fontName);
  [self textLayer].font = fontRef;
  [self textLayer].fontSize = font.pointSize;
  
  CGFontRelease(fontRef);
}
@end

如果你運(yùn)行代碼识樱,你會發(fā)現(xiàn)文本并沒有像素化嗤无,而我們也沒有設(shè)置contentsScale屬性。把CATextLayer作為宿主圖層的另一好處就是視圖自動設(shè)置了contentsScale屬性怜庸。

在這個(gè)簡單的例子中当犯,我們只是實(shí)現(xiàn)了UILabel的一部分風(fēng)格和布局屬性,不過稍微再改進(jìn)一下我們就可以創(chuàng)建一個(gè)支持UILabel所有功能甚至更多功能的LayerLabel類(你可以在一些線上的開源項(xiàng)目中找到)割疾。

如果你打算支持iOS 6及以上嚎卫,基于CATextLayer的標(biāo)簽可能就有有些局限性。但是總得來說宏榕,如果想在app里面充分利用CALayer子類拓诸,用+layerClass來創(chuàng)建基于不同圖層的視圖是一個(gè)簡單可復(fù)用的方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末麻昼,一起剝皮案震驚了整個(gè)濱河市奠支,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抚芦,老刑警劉巖倍谜,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叉抡,居然都是意外死亡枢劝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門卜壕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來您旁,“玉大人,你說我怎么就攤上這事轴捎『缀校” “怎么了蚕脏?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長侦锯。 經(jīng)常有香客問我驼鞭,道長,這世上最難降的妖魔是什么尺碰? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任挣棕,我火速辦了婚禮,結(jié)果婚禮上亲桥,老公的妹妹穿的比我還像新娘洛心。我一直安慰自己,他們只是感情好题篷,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布词身。 她就那樣靜靜地躺著,像睡著了一般番枚。 火紅的嫁衣襯著肌膚如雪法严。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天葫笼,我揣著相機(jī)與錄音深啤,去河邊找鬼。 笑死路星,一個(gè)胖子當(dāng)著我的面吹牛墓塌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奥额,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼访诱!你這毒婦竟也來了垫挨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤触菜,失蹤者是張志新(化名)和其女友劉穎九榔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涡相,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哲泊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了催蝗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片切威。...
    茶點(diǎn)故事閱讀 40,912評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丙号,靈堂內(nèi)的尸體忽然破棺而出先朦,到底是詐尸還是另有隱情缰冤,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布喳魏,位于F島的核電站棉浸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏刺彩。R本人自食惡果不足惜迷郑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望创倔。 院中可真熱鬧嗡害,春花似錦、人聲如沸三幻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽念搬。三九已至抑堡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間朗徊,已是汗流浹背首妖。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爷恳,地道東北人有缆。 一個(gè)月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像温亲,于是被迫代替她去往敵國和親棚壁。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評論 2 361

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