iOS:使用UIView的“drawRect:”与其图层的delagate“drawLayer:inContext:”
我有一个类是UIView
的子类。 我可以通过实现drawRect
方法或通过实现drawLayer:inContext:
这是CALayer
的委托方法来在视图内绘制东西。
我有两个问题:
- 如何决定使用哪种方法? 每个人都有一个用例吗?
-
如果我实现
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:
在实现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.layer
和layer
参数并不总是相同的图层是非常重要的! 后者可能是前者的副本,部分animation已经应用于其属性。 这样,您可以访问正在进行的animation的属性值。
现在图纸按预期工作:
override func drawLayer(layer: CALayer, inContext context: CGContext) { let colorForCustomDrawing = layer.backgroundColor // your custom drawing code }
但是还有两个新问题: setNeedsDisplay()
和像backgroundColor
和opaque
这样的属性不再适用于你的视图。 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:方法,则会覆盖这些值 – 即使它完全为空(即,甚至不会调用超级)。