原文地址:http://www.cocoachina.com/ios/20150105/10827.html
CAShapeLayer
CAShapeLayer是一個通過向量圖形而不是bitmap來繪製的圖層子類。你指定諸如顏色和線寬等屬性,用CGPath來定義想要繪製的圖形,最後CAShapeLayer就自動渲染出來了。當然,你也可以用Core Graphics直接向原始的CALayer的內容中繪製一個路徑,相比直下,使用CAShapeLayer有以下一些優點:
-
渲染快速。CAShapeLayer使用了硬體加速,繪製同一圖形會比用Core Graphics快很多。
-
高效使用記憶體。一個CAShapeLayer不需要像普通CALayer一樣建立一個寄宿圖形,所以無論有多大,都不會佔用太多的記憶體。
-
不會被圖層邊界剪裁掉。一個CAShapeLayer可以在邊界之外繪製。你的圖層路徑不會像在使用Core Graphics的普通CALayer一樣被剪裁掉(如我們在第二章所見)。
-
不會出現畫素化。當你給CAShapeLayer做3D變換時,它不像一個有寄宿圖的普通圖層一樣變得畫素化。
事實上你可以在一個圖層上繪製好幾個不同的形狀。你可以控制一些屬性比如lineWith(線寬,用點表示單位),lineCap(線條結尾的樣子),和lineJoin(線條之間的結合點的樣子);但是在圖層層面你只有一次機會設定這些屬性。如果你想用不同顏色或風格來繪製多個形狀,就不得不為每個形狀準備一個圖層了。
CAShapeLayer屬性是CGPathRef型別,但是我們用UIBezierPath幫助類建立了圖層路徑,這樣我們就不用考慮人工釋放CGPath了
#import "DrawingView.h" #import @interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //create path UIBezierPath *path = [[UIBezierPath alloc] init]; [path moveToPoint:CGPointMake(175, 100)]; ? [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES]; [path moveToPoint:CGPointMake(150, 125)]; [path addLineToPoint:CGPointMake(150, 175)]; [path addLineToPoint:CGPointMake(125, 225)]; [path moveToPoint:CGPointMake(150, 175)]; [path addLineToPoint:CGPointMake(175, 225)]; [path moveToPoint:CGPointMake(100, 150)]; [path addLineToPoint:CGPointMake(200, 150)]; //create shape layer CAShapeLayer *shapeLayer = [CAShapeLayer layer]; shapeLayer.strokeColor = [UIColor redColor].CGColor; shapeLayer.fillColor = [UIColor clearColor].CGColor; shapeLayer.lineWidth = 5; shapeLayer.lineJoin = kCALineJoinRound; shapeLayer.lineCap = kCALineCapRound; shapeLayer.path = path.CGPath; //add it to our view [self.containerView.layer addSublayer:shapeLayer]; } @end
第二章裡面提到了CAShapeLayer為建立圓角檢視提供了一個方法,就是CALayer的cornerRadius屬性(譯者注:其實是在第四章提到的)。雖然使用CAShapeLayer類需要更多的工作,但是它有一個優勢就是可以單獨指定每個角。
我們建立圓角舉行其實就是人工繪製單獨的直線和弧度,但是事實上UIBezierPath有自動繪製圓角矩形的構造方法,下面這段程式碼繪製了一個有三個圓角一個直角的矩形:
//define path parameters CGRect rect = CGRectMake(50, 50, 100, 100); CGSize radii = CGSizeMake(20, 20); UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft; //create path UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
CATextLayer
Core Animation提供了一個CALayer的子類CATextLayer,它以圖層的形式包含了UILabel幾乎所有的繪製特性,並且額外提供了一些新的特性。
CATextLayer也要比UILabel渲染得快得多。很少有人知道在iOS 6及之前的版本,UILabel其實是通過WebKit來實現繪製的,這樣就造成了當有很多文字的時候就會有極大的效能壓力。而CATextLayer使用了Core text,並且渲染得非常快。用CATextLayer來實現一個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
如果你自習看這個文字,你會發現一個奇怪的地方:這些文字有一些畫素化了。這是因為並沒有以Retina的方式渲染,第二章提到了這個contentScale屬性,用來決定圖層內容應該以怎樣的解析度來渲染。contentsScale並不關心螢幕的拉伸因素而總是預設為1.0。如果我們想以Retina的質量來顯示文字,我們就得手動地設定CATextLayer的contentsScale屬性,如下:textLayer.contentsScale = [UIScreen mainScreen].scale;
富文字
UIKit 的 UILabel 允許你通過在 IB 中簡單的拖曳新增文字,但你不能改變文字的顏色和其中的單詞。
Core Graphics/Quartz幾乎允許你做任何系統允許的事情,但你需要為每個字形計算位置,並畫在螢幕上。
CoreText正結合了這兩者!你自己可以完全控制位置、佈局、類似文字大小和顏色這樣的屬性,CoreText將幫你完善其它的東西??類似文字換行、字型呈現等等。
然而,CoreText.framework本身非常龐大,學習成本較高,使用起來也不是很方便,所以一般不是特殊需要,很少會有人去使用它。
隨著iOS6 API的釋出,文字顯示的API越來越完善,其中一個重要的更新是在UITextField,UITextView和UILabel中加入了對AttributedString的支援,實現行距控制,字距控制,段落控制等高階功能也不必再去使用深奧的CoreText框架。
而iOS7的釋出,蘋果又引入了TextKit,TextKit是一個快速而又現代化的文字排版和渲染引擎。
TextKit並沒有新增類,只是在原有的文字顯示控制元件上進行了封裝,可以在平時我們最喜歡使用的UILabel,UITextField,UITextView等控制元件裡面使用,其最主要的作用就是為程式提供文字排版和渲染的功能。
蘋果引入TextKit的目的並非要取代已有的CoreText框架,雖然CoreText的主要作用也是用於文字的排版和渲染,但它是一種先進而又處於底層技術,如果我們需要將文字內容直接渲染到圖形上下文(Graphics context)時,從效能和易用性來考慮,最佳方案就是使用CoreText。而如果我們需要直接利用蘋果提供的一些控制元件(如UITextView、UILabel和UITextField等)對文字進行排版,那麼藉助於UIKit中TextKit提供的API無疑更為方便快捷。
TextKit在文書處理方面具有非常強大的功能,並且開發者可以對TextKit進行定製和擴充套件。據悉,蘋果利用了2年的時間來開發TextKit,相信這對許多開發者來說都是福音。
然而,無論CoreText還是TextKit都是非常龐大的體系,而我們的需求通過一個簡單小巧的AttributedString就可以輕鬆搞定,所以本文的關注點只有一個,那就是AttributedString。
方法一:
NSString *originStr = @"Hello,中秋節!"; //方式一 //建立 NSMutableAttributedString NSMutableAttributedString *attributedStr01 = [[NSMutableAttributedString alloc] initWithString: originStr]; //新增屬性 //給所有字元設定字型為Zapfino,字型高度為15畫素 [attributedStr01 addAttribute: NSFontAttributeName value: [UIFont fontWithName: @"Zapfino" size: 15] range: NSMakeRange(0, originStr.length)]; //分段控制,最開始4個字元顏色設定成藍色 [attributedStr01 addAttribute: NSForegroundColorAttributeName value: [UIColor blueColor] range: NSMakeRange(0, 4)]; //分段控制,第5個字元開始的3個字元,即第5、6、7字元設定為紅色 [attributedStr01 addAttribute: NSForegroundColorAttributeName value: [UIColor redColor] range: NSMakeRange(4, 3)]; //賦值給顯示控制元件label01的 attributedText _label01.attributedText = attributedStr01;
方法二:
//方式二的分段處理 //第一段 NSDictionary *attrDict1 = @{ NSFontAttributeName: [UIFont fontWithName: @"Zapfino" size: 15], NSForegroundColorAttributeName: [UIColor blueColor] }; NSAttributedString *attrStr1 = [[NSAttributedString alloc] initWithString: [originStr substringWithRange: NSMakeRange(0, 4)] attributes: attrDict1]; //第二段 NSDictionary *attrDict2 = @{ NSFontAttributeName: [UIFont fontWithName: @"Zapfino" size: 15], NSForegroundColorAttributeName: [UIColor redColor] }; NSAttributedString *attrStr2 = [[NSAttributedString alloc] initWithString: [originStr substringWithRange: NSMakeRange(4, 3)] attributes: attrDict2]; //第三段 NSDictionary *attrDict3 = @{ NSFontAttributeName: [UIFont fontWithName: @"Zapfino" size: 15], NSForegroundColorAttributeName: [UIColor blackColor] }; NSAttributedString *attrStr3 = [[NSAttributedString alloc] initWithString: [originStr substringWithRange: NSMakeRange(7, originStr.length - 4 - 3)] attributes: attrDict3]; //合併 NSMutableAttributedString *attributedStr03 = [[NSMutableAttributedString alloc] initWithAttributedString: attrStr1]; [attributedStr03 appendAttributedString: attrStr2]; [attributedStr03 appendAttributedString: attrStr3]; _label03.attributedText = attributedStr03;
AttributedString可設定的屬性:
// NSFontAttributeName 設定字型屬性,預設值:字型:Helvetica(Neue) 字號:12 // NSForegroundColorAttributeNam 設定字型顏色,取值為 UIColor物件,預設值為黑色 // NSBackgroundColorAttributeName 設定字型所在區域背景顏色,取值為 UIColor物件,預設值為nil, 透明色 // NSLigatureAttributeName 設定連體屬性,取值為NSNumber 物件(整數),0 表示沒有連體字元,1 表示使用預設的連體字元 // NSKernAttributeName 設定字元間距,取值為 NSNumber 物件(整數),正值間距加寬,負值間距變窄 // NSStrikethroughStyleAttributeName 設定刪除線,取值為 NSNumber 物件(整數) // NSStrikethroughColorAttributeName 設定刪除線顏色,取值為 UIColor 物件,預設值為黑色 // NSUnderlineStyleAttributeName 設定下劃線,取值為 NSNumber 物件(整數),列舉常量 NSUnderlineStyle中的值,與刪除線類似 // NSUnderlineColorAttributeName 設定下劃線顏色,取值為 UIColor 物件,預設值為黑色 // NSStrokeWidthAttributeName 設定筆畫寬度,取值為 NSNumber 物件(整數),負值填充效果,正值中空效果 // NSStrokeColorAttributeName 填充部分顏色,不是字型顏色,取值為 UIColor 物件 // NSShadowAttributeName 設定陰影屬性,取值為 NSShadow 物件 // NSTextEffectAttributeName 設定文字特殊效果,取值為 NSString 物件,目前只有圖版印刷效果可用: // NSBaselineOffsetAttributeName 設定基線偏移值,取值為 NSNumber (float),正值上偏,負值下偏 // NSObliquenessAttributeName 設定字形傾斜度,取值為 NSNumber (float),正值右傾,負值左傾 // NSExpansionAttributeName 設定文字橫向拉伸屬性,取值為 NSNumber (float),正值橫向拉伸文字,負值橫向壓縮文字 // NSWritingDirectionAttributeName 設定文字書寫方向,從左向右書寫或者從右向左書寫 // NSVerticalGlyphFormAttributeName 設定文字排版方向,取值為 NSNumber 物件(整數),0 表示橫排文字,1 表示豎排文字 // NSLinkAttributeName 設定連結屬性,點選後呼叫瀏覽器開啟指定URL地址 // NSAttachmentAttributeName 設定文字附件,取值為NSTextAttachment物件,常用於文字圖片混排 // NSParagraphStyleAttributeName 設定文字段落排版格式,取值為 NSParagraphStyle 物件
富文字的前半段原文地址為:http://blog.csdn.net/mylizh/article/details/39024353
UILabel 的替代品
我們已經證實了CATextLayer比UILabel有著更好的效能表現,同時還有額外的佈局選項並且在iOS 5上支援富文字。但是與一般的標籤比較而言會更加繁瑣一些。如果我們真的在需求一個UILabel的可用替代品,最好是能夠在Interface Builder上建立我們的標籤,而且儘可能地像一般的檢視一樣正常工作。
我們應該繼承UILabel,然後新增一個子圖層CATextLayer並重寫顯示文字的方法。但是仍然會有由UILabel的-drawRect:方法建立的空寄宿圖。而且由於CALayer不支援自動縮放和自動佈局,子檢視並不是主動跟蹤檢視邊界的大小,所以每次檢視大小被更改,我們不得不手動更新子圖層的邊界。
我們真正想要的是一個用CATextLayer作為宿主圖層的UILabel子類,這樣就可以隨著檢視自動調整大小而且也沒有冗餘的寄宿圖啦。
#import "LayerLabel.h" #import @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
上面程式碼演示了一個UILabel子類LayerLabel用CATextLayer繪製它的問題,而不是呼叫一般的UILabel使用的較慢的-drawRect:方法。LayerLabel示例既可以用程式碼實現,也可以在Interface Builder實現,只要把普通的標籤拖入檢視之中,然後設定它的類是LayerLabel就可以了。
如果你執行程式碼,你會發現文字並沒有畫素化,而我們也沒有設定contentsScale屬性。把CATextLayer作為宿主圖層的另一好處就是檢視自動設定了contentsScale屬性。
如果你打算支援iOS 6及以上,基於CATextLayer的標籤可能就有有些侷限性。但是總得來說,如果想在app裡面充分利用CALayer子類,用+layerClass來建立基於不同圖層的檢視是一個簡單可複用的方法。