iOS——Core Animation 知识摘抄(三)
原文地址: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来创建基于不同图层的视图是一个简单可复用的方法。