iOS:使用UIView的'drawRect:'与其图层的委托'drawLayer:inContext:'

时间:2011-02-12 16:39:56

标签: iphone ios delegates core-animation

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

我有两个问题:

  1. 如何决定使用哪种方法?每个人都有一个用例吗?
  2. 如果我实现drawLayer:inContext:,它就被调用(并且drawRect不是,至少就断点可以告诉的那样,即使我没有指定我的视图作为CALayer代表使用:

    [[self layer] setDelegate:self];

    如果我的实例未被定义为图层的委托,那么如何调用委托方法?如果调用drawRect,会阻止调用drawLayer:inContext:的机制是什么?

7 个答案:

答案 0 :(得分:72)

  

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

始终使用drawRect:,绝不使用UIView作为任何CALayer的绘图委托。

  

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

每个UIView实例都是其支持CALayer的绘图委托。这就是[[self layer] setDelegate:self];似乎无所作为的原因。这是多余的。 drawRect:方法实际上是视图图层的绘图委托方法。在内部,UIView实现了drawLayer:inContext:,在其中执行了一些自己的工作,然后调用drawRect:。您可以在调试器中看到它:

drawRect: stacktrace

这就是您实施drawRect:时从未调用drawLayer:inContext:的原因。这也是为什么你永远不应该在自定义CALayer子类中实现任何UIView绘图委托方法的原因。您也应该永远不要查看另一个图层的绘图委托。这将导致各种各样的古怪。

如果您要实施drawLayer:inContext:,因为您需要访问CGContextRef,则可以通过调用drawRect:UIGraphicsGetCurrentContext()内部获取该内容。

答案 1 :(得分:44)

drawRect只应在绝对需要时实施。 drawRect的默认实现包括许多智能优化,例如智能缓存视图的渲染。覆盖它会绕过所有这些优化。那很糟。有效地使用图层绘制方法几乎总是优于自定义drawRect。 Apple经常使用UIView作为CALayer的代理 - 实际上每UIView is the delegate of it's layer。你可以看到如何在几个Apple样本中自定义UIView中的图层绘图,包括(此时)ZoomingPDFViewer。

虽然使用drawRect很常见,但至少从2002/2003年以来,这种做法一直受到劝阻。沿着这条道路前进的原因并不多。

Advanced Performance Optimization on iPhone OS(幻灯片15)

Core Animation Essentials

Understanding UIKit Rendering

Technical Q&A QA1708: Improving Image Drawing Performance on iOS

View Programming Guide: Optimizing View Drawing

答案 2 :(得分:12)

以下是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
{
    ...
}

答案 3 :(得分:8)

是否将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在动画播出时没有返回正确的值(即当前值)。相反,它返回最终值(即动画完成时的值)。

要在动画期间获取当前值,您必须访问传递给{{1的backgroundColor 参数layer }}。您还必须绘制到drawLayer(_:inContext:) 参数

了解传递给context的视图self.layerlayer参数并不总是同一层非常重要!后者可能是前者的副本,部分动画已应用于其属性。这样您就可以访问正在进行的动画的正确属性值。

现在绘图按预期工作:

drawLayer(_:inContext:)

但是有两个新问题:override func drawLayer(layer: CALayer, inContext context: CGContext) { let colorForCustomDrawing = layer.backgroundColor // your custom drawing code } 并且setNeedsDisplay()backgroundColor等多个属性不再适用于您的观点。 opaque不再将呼叫和更改转发到自己的图层。

UIView只会在您的视图实现setNeedsDisplay()时执行某些操作。如果函数实际执行某些操作并不重要,但UIKit会使用它来确定您是否进行自定义绘图。

由于不再调用drawRect(_:)自己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(_:)

答案 4 :(得分:7)

在iOS上,视图与其图层之间的重叠非常大。默认情况下,视图是其图层的委托,并实现图层的drawLayer:inContext:方法。据我了解,drawRect:drawLayer:inContext:在这种情况下或多或少相同。可能只会在您的子类未实现drawLayer:inContext:的情况下调用drawRect:调用drawRect:drawLayer:inContext:的默认实现。

  

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

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

答案 5 :(得分:2)

Apple Documentation有这样的说法:“还有其他方法可以提供视图的内容,例如直接设置底层的内容,但是覆盖drawRect:方法是最常用的技术。”

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

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

答案 6 :(得分:0)

对于具有自定义内容的支持图层的视图,您应继续覆盖该视图的方法来进行绘制。层支持的视图自动使自己成为其层的委托并实现所需的委托方法,并且您不应更改该配置。相反,您应该实现视图的drawRect:方法来绘制内容。Core Animation Programming Guide