iOS——Core Animation 知識摘抄(三)

lihaiyin發表於2015-04-23

原文地址: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來建立基於不同圖層的檢視是一個簡單可複用的方法。

相關文章