# Custom Drawing
给`contents`赋CGImage的值不是唯一的设置寄宿图的方法。我们也可以直接用Core Graphics直接绘制寄宿图。能够通过继承UIView并实现`-drawRect:`方法来自定义绘制。
`-drawRect:` 方法没有默认的实现,因为对UIView来说,寄宿图并不是必须的,它不在意那到底是单调的颜色还是有一个图片的实例。如果UIView检测到`-drawRect:` 方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以 `contentsScale`的值。
如果你不需要寄宿图,那就不要创建这个方法了,这会造成CPU资源和内存的浪费,这也是为什么苹果建议:如果没有自定义绘制的任务就不要在子类中写一个空的-drawRect:方法。
当视图在屏幕上出现的时候 `-drawRect:`方法就会被自动调用。`-drawRect:`方法里面的代码利用Core Graphics去绘制一个寄宿图,然后内容就会被缓存起来直到它需要被更新(通常是因为开发者调用了`-setNeedsDisplay`方法,尽管影响到表现效果的属性值被更改时,一些视图类型会被自动重绘,如`bounds`属性)。虽然`-drawRect:`方法是一个UIView方法,事实上都是底层的CALayer安排了重绘工作和保存了因此产生的图片。
CALayer有一个可选的`delegate`属性,实现了`CALayerDelegate`协议,当CALayer需要一个内容特定的信息时,就会从协议中请求。CALayerDelegate是一个非正式协议,其实就是说没有CALayerDelegate @protocol可以让你在类里面引用啦。你只需要调用你想调用的方法,CALayer会帮你做剩下的。(`delegate`属性被声明为id类型,所有的代理方法都是可选的)。
当需要被重绘时,CALayer会请求它的代理给他一个寄宿图来显示。它通过调用下面这个方法做到的:
~~~
(void)displayLayer:(CALayerCALayer *)layer;
~~~
趁着这个机会,如果代理想直接设置`contents`属性的话,它就可以这么做,不然没有别的方法可以调用了。如果代理不实现`-displayLayer:`方法,CALayer就会转而尝试调用下面这个方法:
~~~
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
~~~
在调用这个方法之前,CALayer创建了一个合适尺寸的空寄宿图(尺寸由`bounds`和`contentsScale`决定)和一个Core Graphics的绘制上下文环境,为绘制寄宿图做准备,他作为ctx参数传入。
让我们来继续第一章的项目让它实现CALayerDelegate并做一些绘图工作吧(见清单2.5).图2.12是他的结果
清单2.5 实现CALayerDelegate
~~~
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];

//create sublayer
CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
blueLayer.backgroundColor = [UIColor blueColor].CGColor;
//set controller as layer delegate
blueLayer.delegate = self;
//ensure that layer backing image uses correct scale
blueLayer.contentsScale = [UIScreen mainScreen].scale; //add layer to our view
[self.layerView.layer addSublayer:blueLayer];
//force layer to redraw
[blueLayer display];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
//draw a thick red circle
CGContextSetLineWidth(ctx, 10.0f);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextStrokeEllipseInRect(ctx, layer.bounds);
}
@end
~~~
![](https://box.kancloud.cn/2015-12-24_567bc1ed4591f.png)
图2.12 实现CALayerDelegate来绘制图层
注意一下一些有趣的事情:
* 我们在blueLayer上显式地调用了`-display`。不同于UIView,当图层显示在屏幕上时,CALayer不会自动重绘它的内容。它把重绘的决定权交给了开发者。
* 尽管我们没有用`masksToBounds`属性,绘制的那个圆仍然沿边界被裁剪了。这是因为当你使用CALayerDelegate绘制寄宿图的时候,并没有对超出边界外的内容提供绘制支持。
现在你理解了CALayerDelegate,并知道怎么使用它。但是除非你创建了一个单独的图层,你几乎没有机会用到CALayerDelegate协议。因为当UIView创建了它的宿主图层时,它就会自动地把图层的delegate设置为它自己,并提供了一个`-displayLayer:`的实现,那所有的问题就都没了。
当使用寄宿了视图的图层的时候,你也不必实现`-displayLayer:`和`-drawLayer:inContext:`方法来绘制你的寄宿图。通常做法是实现UIView的`-drawRect:`方法,UIView就会帮你做完剩下的工作,包括在需要重绘的时候调用`-display`方法。
- Introduction
- 1. 图层树
- 1.1 图层与视图
- 1.2 图层的能力
- 1.3 使用图层
- 1.4 总结
- 2. 寄宿图
- 2.1 contents属性
- 2.2 Custom Drawing
- 2.3 总结
- 3. 图层几何学
- 3.1 布局
- 3.2 锚点
- 3.3 坐标系
- 3.4 Hit Testing
- 3.5 自动布局
- 3.6 总结
- 4. 视觉效果
- 4.1 圆角
- 4.2 图层边框
- 4.3 阴影
- 4.4 图层蒙板
- 4.5 拉伸过滤
- 4.6 组透明
- 4.7 总结
- 5. 变换
- 5.1 仿射变换
- 5.2 3D变换
- 5.3 固体对象
- 5.4 总结
- 6. 专用图层
- 6.1 CAShapeLayer
- 6.2 CATextLayer
- 6.3 CATransformLayer
- 6.4 CAGradientLayer
- 6.5 CAReplicatorLayer
- 6.6 CAScrollLayer
- 6.7 CATiledLayer
- 6.8 CAEmitterLayer
- 6.9 CAEAGLLayer
- 6.10 AVPlayerLayer
- 6.11 总结
- 7. 隐式动画
- 7.1 事务
- 7.2 完成块
- 7.3 图层行为
- 7.4 呈现与模型
- 7.5 总结
- 8. 显式动画
- 8.1 属性动画
- 8.2 动画组
- 8.3 过渡
- 8.4 在动画过程中取消动画
- 8.5 总结
- 9. 图层时间
- 9.1 CAMediaTiming协议
- 9.2 层级关系时间
- 9.3 手动动画
- 9.4 总结
- 10. 缓冲
- 10.1 动画速度
- 10.2 自定义缓冲函数
- 10.3 总结
- 11. 基于定时器的动画
- 11.1 定时帧
- 11.2 物理模拟
- 12. 性能调优
- 12.1. CPU VS GPU
- 12.2 测量,而不是猜测
- 12.3 Instruments
- 12.4 总结
- 13. 高效绘图
- 13.1 软件绘图
- 13.2 矢量图形
- 13.3 脏矩形
- 13.4 异步绘制
- 13.5 总结
- 14. 图像IO
- 14.1 加载和潜伏
- 14.2 缓存
- 14.3 文件格式
- 14.4 总结
- 15. 图层性能
- 15.1 隐式绘制
- 15.2 离屏渲染
- 15.3 混合和过度绘制
- 15.4 减少图层数量
- 15.5 总结