我有一个类,它是UIView
的子类。我可以通过实现drawRect
方法或通过实现drawLayer:inContext:
(CALayer
的委托方法)来绘制视图中的内容。
我有两个问题:
drawLayer:inContext:
,它被调用(并且drawRect
不是,至少就断点可以告诉),即使我没有通过使用以下方式将我的视图指定为CALayer
委托:
[[self layer] setDelegate:self];
如果我的实例未被定义为图层的委托,那么如何调用委托方法?如果调用drawRect
,有什么机制阻止drawLayer:inContext:
被调用?如何决定使用哪种方法?每个都有一个用例吗?
总是使用drawRect:
,永远不要使用UIView
作为任何CALayer
的绘图代表。
如果我的实例未被定义为图层的委托,那么如何调用委托方法?如果调用
drawLayer:inContext:
,什么机制可以阻止调用drawRect?
每个UIView
实例都是其支持CALayer
的绘图代表。这就是为什么[[self layer] setDelegate:self];
似乎什么都不做。这是多余的。 drawRect:
方法实际上是视图层的绘图委托方法。在内部,UIView
实现了drawLayer:inContext:
,在那里它做了一些自己的东西,然后调用drawRect:
。您可以在调试器中看到它:
这就是为什么drawRect:
在你实施drawLayer:inContext:
时从未被调用过的原因。这也是为什么你永远不应该在自定义CALayer
子类中实现任何UIView
绘图委托方法的原因。您也应该永远不要查看另一个图层的绘图委托。这将导致各种古怪。
如果您正在实施drawLayer:inContext:
,因为您需要访问CGContextRef
,您可以通过调用drawRect:
从UIGraphicsGetCurrentContext()
内部获取。
drawRect
只应在绝对需要时实施。 drawRect
的默认实现包括许多智能优化,例如智能地缓存视图的渲染。覆盖它会绕过所有这些优化。那很糟。有效地使用图层绘制方法几乎总是优于自定义drawRect
。 Apple经常使用UIView
作为CALayer
的代表 - 事实上,每个UIView is the delegate of it's layer。您可以在几个Apple示例中看到如何在UIView中自定义图层绘图,包括(此时)ZoomingPDFViewer。
尽管drawRect
的使用很常见,但至少从2002/2003年IIRC开始,这种做法一直被劝阻。沿着这条道路走的路没有很多好理由。
Advanced Performance Optimization on iPhone OS(幻灯片15)
Technical Q&A QA1708: Improving Image Drawing Performance on iOS
以下是Apple的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(_:)
(或两者)来自定义绘图代码取决于在动画时是否需要访问图层属性的当前值。
今天我在实施my own Label class时遇到了与这两个函数相关的各种渲染问题。在查看文档,做一些反复试验,反编译UIKit并检查Apple的Custom Animatable Properties example后,我对它的工作原理有了很好的认识。
drawRect(_:)
如果您不需要在动画期间访问图层/视图属性的当前值,则只需使用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
}
当您测试代码时,您会注意到backgroundColor
在动画播出时没有返回正确的值(即当前值)。相反,它返回最终值(即动画完成时的值)。
要在动画期间获取当前值,您必须访问传递给backgroundColor
的layer
参数的drawLayer(_:inContext:)
。而且你还必须绘制到context
参数。
非常重要的是要知道传递给self.layer
的视图的layer
和drawLayer(_:inContext:)
参数并不总是同一层!后者可能是前者的副本,部分动画已经应用于其属性。这样您就可以访问正在进行的动画的正确属性值。
现在绘图按预期工作:
override func drawLayer(layer: CALayer, inContext context: CGContext) {
let colorForCustomDrawing = layer.backgroundColor
// your custom drawing code
}
但是有两个新问题:setNeedsDisplay()
和backgroundColor
和opaque
等几个属性不再适用于您的观点。 UIView
不再将呼叫和更改转发到自己的层。
setNeedsDisplay()
只会在您的视图实现drawRect(_:)
时执行某些操作。如果函数实际执行某些操作并不重要,但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(_:)
:
override func drawRect(rect: CGRect) {
// your custom drawing code
}
如果您在动画时需要访问视图/图层属性的当前值,请使用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:
或多或少相同。可能,drawLayer:inContext:
的默认实现调用drawRect:
,或drawRect:
仅在您的子类未实现drawLayer:inContext:
时调用。
如何决定使用哪种方法?每个都有一个用例吗?
这并不重要。为了遵循惯例,我通常会使用drawRect:
并保留使用drawLayer:inContext:
,因为我实际上必须绘制不属于视图的自定义子图层。
Apple Documentation有这样说:“还有其他方法可以提供视图的内容,例如直接设置底层的内容,但是覆盖drawRect:方法是最常用的技术。”
但它没有涉及任何细节,所以这应该是一个线索:除非你真的想弄脏你的手,否则不要这样做。
UIView的图层委托指向UIView。但是,UIView的行为会有所不同,具体取决于是否实现了drawRect :.例如,如果直接在图层上设置属性(例如背景颜色或角半径),如果您有drawRect:方法,则会覆盖这些值 - 即使它完全为空(即甚至不调用超级)。
对于具有自定义内容的图层支持视图,您应该继续覆盖视图的方法来进行绘制。图层支持的视图自动使其自身成为其图层的委托,并实现所需的委托方法,您不应更改该配置。相反,您应该实现视图的drawRect:方法来绘制content.Core Animation Programming Guide