iOS:使用UIView的“drawRect:”与其图层的delagate“drawLayer:inContext:”

我有一个类是UIView的子类。 我可以通过实现drawRect方法或通过实现drawLayer:inContext:这是CALayer的委托方法来在视图内绘制东西。

我有两个问题:

  1. 如何决定使用哪种方法? 每个人都有一个用例吗?
  2. 如果我实现drawLayer:inContext:它被调用(并且drawRect不是,至less就放一个断点可以告诉),即使我没有分配我的视图作为CALayer委托通过使用:

    [[self layer] setDelegate:self];

    如果我的实例没有被定义为图层的委托,那么如何调用委托方法呢? 如果drawLayer:inContext:被调用,什么机制可以防止drawRect被调用?

如何决定使用哪种方法? 每个人都有一个用例吗?

总是使用drawRect:并且从不使用UIView作为任何CALayer的绘图CALayer

如果我的实例没有被定义为图层的委托,那么如何调用委托方法呢? 如果drawLayer:inContext:被调用,什么机制可以防止drawRect被调用?

每个UIView实例都是其支持CALayer的绘图委托。 这就是为什么[[self layer] setDelegate:self]; 似乎什么都不做。 这是多余的。 drawRect:方法实际上是视图图层的绘图委托方法。 在内部, UIView实现了drawLayer:inContext:它自己做了一些东西,然后调用drawRect: drawLayer:inContext: 你可以在debugging器中看到它:

drawRect:stacktrace

这就是为什么drawRect:在实现drawLayer:inContext:时从未被调用的原因。 这也是为什么你不应该在自定义的UIView子类中实现任何CALayer绘图委托方法。 您也不应该为另一个图层查看绘图委托。 那会造成各种各样的怪异。

如果你正在实现drawLayer:inContext:因为你需要访问CGContextRef ,你可以通过调用UIGraphicsGetCurrentContext()来从drawRect:获取它。

drawRect只能在绝对需要时才能实现。 drawRect的默认实现包括许多智能优化,比如智能caching视图的渲染。 覆盖它绕过了所有这些优化。 那很糟。 有效地使用图层绘制方法将几乎总是胜过自定义drawRect 。 苹果经常使用一个UIView作为CALayer的委托 – 事实上,每个UIView都是它的图层委托 。 你可以看到如何在多个Apple样本(包括ZoomingPDFViewer)中自定义UIView中的图层。

虽然使用drawRect是常见的,但从2002/2003年至今,IIRC已经不鼓励这种做法。 没有太多好的理由留在这条路上。

iPhone OS上的高级性能优化 (幻灯片15)

核心animation要领

了解UIKit渲染

技术问答QA1708:提高iOS上的图像绘制性能

查看编程指南:优化视图绘制

这里是来自苹果公司的Sample ZoomingPDFViewer代码:

 -(void)drawRect:(CGRect)r { // UIView uses the existence of -drawRect: to determine if it should allow its CALayer // to be invalidated, which would then lead to the layer creating a backing store and // -drawLayer:inContext: being called. // By implementing an empty -drawRect: method, we allow UIKit to continue to implement // this logic, while doing our real drawing work inside of -drawLayer:inContext: } -(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context { ... } 

对于自定义绘图代码,是否使用drawLayer(_:inContext:)drawRect(_:) (或两者)取决于在animation时是否需要访问图层属性的当前值。

在实现我自己的Label类的时候, 我在今天挣扎着与这两个函数有关的各种呈现问​​题。 检查完文档后,做一些反复试验,反编译UIKit,并检查苹果的“ 自定义animation属性”示例,我对它的工作原理有了很好的理解。

drawRect(_:)

如果在animation过程中不需要访问layer / view属性的当前值,则可以使用drawRect(_:)来执行自定义绘制。 一切都会正常工作。

 override func drawRect(rect: CGRect) { // your custom drawing code } 

drawLayer(_:inContext:)

比方说,你想在你的自定义绘图代码中使用backgroundColor

 override func drawRect(rect: CGRect) { let colorForCustomDrawing = self.layer.backgroundColor // your custom drawing code } 

当你testing你的代码时,你会注意到,在animation正在进行时, backgroundColor不会返回正确的(即当前)值。 而是返回最终值(即animation完成时的值)。

为了在animation期间获取当前值,必须访问传递给drawLayer(_:inContext:)layer 参数backgroundColor 。 而且还必须绘制到context 参数

知道传递给drawLayer(_:inContext:)的视图的self.layerlayer参数并不总是相同的图层是非常重要的! 后者可能是前者的副本,部分animation已经应用于其属性。 这样,您可以访问正在进行的animation的属性值。

现在图纸按预期工作:

 override func drawLayer(layer: CALayer, inContext context: CGContext) { let colorForCustomDrawing = layer.backgroundColor // your custom drawing code } 

但是还有两个新问题: setNeedsDisplay()和像backgroundColoropaque这样的属性不再适用于你的视图。 UIView不再转发调用和更改到自己的图层。

如果你的视图实现了drawRect(_:) setNeedsDisplay()只会做一些事情。 如果函数实际上做了什么并不重要,但UIKit使用它来确定您是否执行自定义绘制。

该属性可能不再工作了,因为UIView自己的drawLayer(_:inContext:)不再被调用。

所以解决办法很简单。 只需调用超类的drawLayer(_:inContext:)实现并实现一个空的drawRect(_:)

 override func drawLayer(layer: CALayer, inContext context: CGContext) { super.drawLayer(layer, inContext: context) let colorForCustomDrawing = layer.backgroundColor // your custom drawing code } override func drawRect(rect: CGRect) { // Although we use drawLayer(_:inContext:) we still need to implement this method. // UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer. } 

概要

使用drawRect(_:) ,只要你没有在animation期间属性返回错误值的问题:

 override func drawRect(rect: CGRect) { // your custom drawing code } 

如果需要在animation时访问视图/图层属性的当前值,请使用drawLayer(_:inContext:) drawRect(_:)

 override func drawLayer(layer: CALayer, inContext context: CGContext) { super.drawLayer(layer, inContext: context) let colorForCustomDrawing = layer.backgroundColor // your custom drawing code } override func drawRect(rect: CGRect) { // Although we use drawLayer(_:inContext:) we still need to implement this method. // UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer. } 

在iOS上,视图与其图层之间的重叠非常大。 默认情况下,视图是其图层的委托,并实现图层的drawLayer:inContext:方法。 据我所知,在这种情况下, drawRect:drawLayer:inContext:或多或less是等价的。 drawLayer:inContext: calls drawRect:drawRect:的默认实现可能只在drawLayer:inContext:未被子类实现的情况下调用。

如何决定使用哪种方法? 每个人都有一个用例吗?

这并不重要。 遵循约定,我通常会使用drawRect:并保留drawLayer:inContext:当我实际上必须绘制不属于视图的自定义子图层时。

苹果文档有这样的说法:“还有其他的方式来提供视图的内容,比如直接设置底层的内容,但是覆盖drawRect:方法是最常用的技术。

但是它没有涉及到任何细节,所以这应该是一个线索:不要这样做,除非你真的想弄脏你的手。

UIView的图层委托指向UIView。 但是,UIView的行为有所不同,具体取决于是否实现了drawRect:。 例如,如果直接设置图层上的属性(如背景颜色或其圆angular半径),则如果您有drawRect:方法,则会覆盖这些值 – 即使它完全为空(即,甚至不会调用超级)。