2.2 Custom Drawing

最后更新于:2022-04-01 10:08:16

# 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://docs.gechiui.com/gc-content/uploads/sites/kancloud/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`方法。
';