专用图层
复杂的组织都是专门化的
Catharine R. Stimpson
如果你依然在编程的世界里迷茫,不知道自己的未来规划,小编给大家推荐一个IOS高级交流群:458839238 里面可以与大神一起交流并走出迷茫。小白可进群免费领取学习资料,看看前辈们是如何在编程的世界里傲然前行!
群内提供数据结构与算法、底层进阶、swift、逆向、整合面试题等免费资料
附上一份收集的各大厂面试题(附答案) ! 群文件直接获取
各大厂面试题
到目前为止,我们已经探讨过CALayer
类了,同时我们也了解到了一些非常有用的绘图和动画功能。但是Core Animation图层不仅仅能作用于图片和颜色而已。本章就会学习其他的一些图层类,进一步扩展使用Core Animation绘图的能力。
CAShapeLayer
在第四章『视觉效果』我们学习到了不使用图片的情况下用CGPath
去构造任意形状的阴影。如果我们能用同样的方式创建相同形状的图层就好了。
CAShapeLayer
是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath
来定义想要绘制的图形,最后CAShapeLayer
就自动渲染出来了。当然,你也可以用Core Graphics直接向原始的CALyer
的内容中绘制一个路径,相比直下,使用CAShapeLayer
有以下一些优点:
- 渲染快速。
CAShapeLayer
使用了硬件加速,绘制同一图形会比用Core Graphics快很多。 - 高效使用内存。一个
CAShapeLayer
不需要像普通CALayer
一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。 - 不会被图层边界剪裁掉。一个
CAShapeLayer
可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer
一样被剪裁掉(如我们在第二章所见)。 - 不会出现像素化。当你给
CAShapeLayer
做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。
创建一个CGPath
CAShapeLayer
可以用来绘制所有能够通过CGPath
来表示的形状。这个形状不一定要闭合,图层路径也不一定要不可破,事实上你可以在一个图层上绘制好几个不同的形状。你可以控制一些属性比如lineWith
(线宽,用点表示单位),lineCap
(线条结尾的样子),和lineJoin
(线条之间的结合点的样子);但是在图层层面你只有一次机会设置这些属性。如果你想用不同颜色或风格来绘制多个形状,就不得不为每个形状准备一个图层了。
清单6.1 的代码用一个CAShapeLayer
渲染一个简单的火柴人。CAShapeLayer
属性是CGPathRef
类型,但是我们用UIBezierPath
帮助类创建了图层路径,这样我们就不用考虑人工释放CGPath
了。图6.1是代码运行的结果。虽然还不是很完美,但是总算知道了大意对吧!
清单6.1 用CAShapeLayer
绘制一个火柴人
#import "DrawingView.h"
#import <QuartzCore/QuartzCore.h>
@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
图6.1 用CAShapeLayer
绘制一个简单的火柴人
圆角
第二章里面提到了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];
我们可以通过这个图层路径绘制一个既有直角又有圆角的视图。如果我们想依照此图形来剪裁视图内容,我们可以把CAShapeLayer
作为视图的宿主图层,而不是添加一个子视图(图层蒙板的详细解释见第四章『视觉效果』)。
CATextLayer
用户界面是无法从一个单独的图片里面构建的。一个设计良好的图标能够很好地表现一个按钮或控件的意图,不过你迟早都要需要一个不错的老式风格的文本标签。
如果你想在一个图层里面显示文字,完全可以借助图层代理直接将字符串使用Core Graphics写入图层的内容(这就是UILabel的精髓)。如果越过寄宿于图层的视图,直接在图层上操作,那其实相当繁琐。你要为每一个显示文字的图层创建一个能像图层代理一样工作的类,还要逻辑上判断哪个图层需要显示哪个字符串,更别提还要记录不同的字体,颜色等一系列乱七八糟的东西。
万幸的是这些都是不必要的,Core Animation提供了一个CALayer
的子类CATextLayer
,它以图层的形式包含了UILabel
几乎所有的绘制特性,并且额外提供了一些新的特性。
同样,CATextLayer
也要比UILabel
渲染得快得多。很少有人知道在iOS 6及之前的版本,UILabel
其实是通过WebKit来实现绘制的,这样就造成了当有很多文字的时候就会有极大的性能压力。而CATextLayer
使用了Core text,并且渲染得非常快。
让我们来尝试用CATextLayer
来显示一些文字。清单6.2的代码实现了这一功能,结果如图6.2所示。
清单6.2 用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
图6.2 用CATextLayer
来显示一个纯文本标签
如果你仔细看这个文本,你会发现一个奇怪的地方:这些文本有一些像素化了。这是因为并没有以Retina的方式渲染,第二章提到了这个contentScale
属性,用来决定图层内容应该以怎样的分辨率来渲染。contentsScale
并不关心屏幕的拉伸因素而总是默认为1.0。如果我们想以Retina的质量来显示文字,我们就得手动地设置CATextLayer
的contentsScale
属性,如下:
textLayer.contentsScale = [UIScreen mainScreen].scale;
这样就解决了这个问题(如图6.3)
图6.3 设置contentsScale
来匹配屏幕
CATextLayer
的font
属性不是一个UIFont
类型,而是一个CFTypeRef
类型。这样可以根据你的具体需要来决定字体属性应该是用CGFontRef
类型还是CTFontRef
类型(Core Text字体)。同时字体大小也是用fontSize
属性单独设置的,因为CTFontRef
和CGFontRef
并不像UIFont一样包含点大小。这个例子会告诉你如何将UIFont
转换成CGFontRef
。
另外,CATextLayer
的string
属性并不是你想象的NSString
类型,而是id
类型。这样你既可以用NSString
也可以用NSAttributedString
来指定文本了(注意,NSAttributedString
并不是NSString
的子类)。属性化字符串是iOS用来渲染字体风格的机制,它以特定的方式来决定指定范围内的字符串的原始信息,比如字体,颜色,字重,斜体等。
富文本
iOS 6中,Apple给UILabel
和其他UIKit文本视图添加了直接的属性化字符串的支持,应该说这是一个很方便的特性。不过事实上从iOS3.2开始CATextLayer
就已经支持属性化字符串了。这样的话,如果你想要支持更低版本的iOS系统,CATextLayer
无疑是你向界面中增加富文本的好办法,而且也不用去跟复杂的Core Text打交道,也省了用UIWebView
的麻烦。
让我们编辑一下示例使用到NSAttributedString
(见清单6.3).iOS 6及以上我们可以用新的NSTextAttributeName
实例来设置我们的字符串属性,但是练习的目的是为了演示在iOS 5及以下,所以我们用了Core Text,也就是说你需要把Core Text framework添加到你的项目中。否则,编译器是无法识别属性常量的。
图6.4是代码运行结果(注意那个红色的下划线文本)
清单6.3 用NSAttributedString实现一个富文本标签。
#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