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

Ita*_*atz 72 iphone delegates core-animation ios

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

我有两个问题:

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

    [[self layer] setDelegate:self];

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

Nat*_*ror 72

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

始终使用drawRect:,永远不要使用任何UIView作为绘图委托CALayer.

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

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

drawRect:stacktrace

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

如果您正在实施,drawLayer:inContext:因为您需要访问CGContextRef,您可以drawRect:通过调用从内部获取UIGraphicsGetCurrentContext().

  • 这不是正确的答案.只要有可能,我们应该使用CAlayer而不是覆盖drawRect来避免潜在的性能问题.参考Apple的WWDC 2012视频 - iOS App性能:图形和动画(链接到网站https://developer.apple.com/videos/wwdc/2012/).从11.40开始,Apple工程师专门针对这种情况. (17认同)
  • http://developer.apple.com/library/ios/#DOCUMENTATION/WindowsViews/Conceptual/ViewPG_iPhoneOS/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW10"还有其他方法可以提供视图的内容,例如直接设置底层的内容,但覆盖drawRect:方法是最常用的技术." (3认同)
  • Nathan,我评论了你的声明"总是使用drawRect:".这是不正确的.Apple建议使用CALayer或drawLayer:inContext,除非绝对需要drawRect覆盖.其中的详细信息在WWDC 2012视频中.你是正确的,通过覆盖drawRect(但不是唯一的解决方案)可以很容易地实现某些情况,例如矢量绘图.但是,在大多数情况下,CALayer提供优于drawRect的性能优势.因此,答案应该是"逐案". (3认同)
  • @user2734323问题不是关于使用“UIView”与“drawRect:”或“CALayer”的权衡。这是关于在“UIView”子类中实现“[CALayerDelegate drawLayer:inContext:]”。在某些情况下,“drawRect:”肯定是首选。您甚至在评论中说“只要有可能”。如果 OP 正在绘制的内容无法通过声明“CALayer”属性来描述,例如具有多个形状/路径、阴影等的矢量绘图,该怎么办? (2认同)
  • “总是使用`drawRect:`”是为了回答OP是否应该在`UIView`子类中实现`drawRect:`或`drawLayer:inContext:`的问题。我想不出在“UIView”子类中实现“drawLayer:inContext:”的一个充分理由。至于尽可能使用“CALayer”(及其子类),我完全同意,并且至少从 2009 年起我就一直在宣扬这个福音。证明在这里:https://github.com/neror(警告,其中一些东西很*旧*):) (2认同)

que*_*ish 44

drawRect应该只在绝对需要时实施.默认实现drawRect包括许多智能优化,例如智能缓存视图的渲染.覆盖它会绕过所有这些优化.那很糟.有效地使用图层绘制方法几乎总是优于自定义drawRect.Apple 经常使用a UIView作为委托CALayer- 实际上,每个UIView都是它的层的委托.您可以在几个Apple示例中看到如何在UIView中自定义图层绘图,包括(此时)ZoomingPDFViewer.

虽然使用drawRect是常见的,但至少从2002/2003年IIRC开始,这种做法已被劝阻.沿着这条道路走的路没有很多好理由.

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

核心动画要点

了解UIKit渲染

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

查看编程指南:优化视图

  • OMG,这是一个很棒的演示!我只是挺身而出......我能感觉到我的生命值和法力值增加了! (5认同)
  • 请参阅WWDC 2012会话"iOS App性能:图形和动画".这可能就像你要达到你想要的那样接近.https://developer.apple.com/videos/wwdc/2012/ (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
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • 否...评论意味着IF -drawRect:存在,UIKit的行为不同(即UIKit查看视图是否响应ToSelector drawRect:,并且基于此,其CALayer可能会或可能不会被无效). (7认同)

flu*_*nic 9

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

今天在实现自己的Label类时遇到了与这两个函数相关的各种渲染问题.在查看文档,进行一些反复试验,反编译UIKit并检查Apple的Custom Animatable Properties示例后,我对它的工作方式有了很好的理解.

drawRect(_:)

如果您不需要在动画期间访问图层/视图属性的当前值,则只需使用它drawRect(_:)来执行自定义绘图.一切都会好起来的.

override func drawRect(rect: CGRect) {
    // your custom drawing code
}
Run Code Online (Sandbox Code Playgroud)

drawLayer(_:inContext:)

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

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

当您测试代码时,您会注意到backgroundColor动画在飞行中时不会返回正确的值(即当前值).相反,它返回最终值(即动画完成时的值).

要在动画期间获取当前值,您必须访问传递给backgroundColorlayer 参数drawLayer(_:inContext:).而且你还必须绘制context 参数.

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

现在绘图按预期工作:

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

但是有两个新问题: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.
}
Run Code Online (Sandbox Code Playgroud)

摘要

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

override func drawRect(rect: CGRect) {
    // your custom drawing code
}
Run Code Online (Sandbox Code Playgroud)

使用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.
}
Run Code Online (Sandbox Code Playgroud)


Ole*_*ann 7

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

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

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