iOS:使用 UIView 的 'drawRect:' 与其层的委托 'drawLayer:inContext:'
Posted
技术标签:
【中文标题】iOS:使用 UIView 的 \'drawRect:\' 与其层的委托 \'drawLayer:inContext:\'【英文标题】:iOS: Using UIView's 'drawRect:' vs. its layer's delegate 'drawLayer:inContext:'iOS:使用 UIView 的 'drawRect:' 与其层的委托 'drawLayer:inContext:' 【发布时间】:2011-02-12 16:39:56 【问题描述】:我有一个类是UIView
的子类。我可以通过实现drawRect
方法或通过实现drawLayer:inContext:
(CALayer
的委托方法)在视图中绘制内容。
我有两个问题:
-
如何决定使用哪种方法?每个都有一个用例吗?
如果我实现drawLayer:inContext:
,它就会被调用(而drawRect
不是,至少就放置断点而言),即使我没有将我的视图分配为CALayer
委托使用:
[[self layer] setDelegate:self];
如果我的实例未定义为层的委托,怎么会调用委托方法?如果drawLayer:inContext:
被调用,什么机制可以防止drawRect
被调用?
【问题讨论】:
【参考方案1】:如何决定使用哪种方法?每个都有用例吗?
始终使用drawRect:
,并且永远不要使用UIView
作为任何CALayer
的绘图委托。
如果我的实例未定义为层的委托,怎么会调用委托方法?如果调用
drawLayer:inContext:
,什么机制可以防止调用drawRect?
每个UIView
实例都是其支持CALayer
的绘图委托。这就是为什么[[self layer] setDelegate:self];
似乎什么也没做。这是多余的。 drawRect:
方法实际上是视图层的绘图委托方法。在内部,UIView
实现了drawLayer:inContext:
,它自己做一些事情,然后调用drawRect:
。你可以在调试器中看到:
这就是为什么在您实现drawLayer:inContext:
时从未调用过drawRect:
。这也是为什么您永远不应该在自定义UIView
子类中实现任何CALayer
绘图委托方法。您也不应该将任何视图作为另一层的绘图委托。这会导致各种古怪。
如果您因为需要访问CGContextRef
而实现drawLayer:inContext:
,则可以通过调用UIGraphicsGetCurrentContext()
从drawRect:
内部获取。
【讨论】:
我将它作为遵循 Apple 的代码示例AccelerometerGraph
的一部分来实现。我猜想在那个代码示例中,他们实现了drawLayer:inContext:
,因为除了视图的根层之外,还有一些层添加到层层次结构中,而且实际上委托不是UIView
实例。
developer.apple.com/library/ios/#DOCUMENTATION/WindowsViews/… " 还有其他方法可以提供视图的内容,例如直接设置底层的内容,但覆盖 drawRect: 方法是最常用的技术。"
嗨,Nathan,我对 iOS 中的自定义绘图非常陌生。我有一个子类 UIView 类,我需要在 3 个不同的图层上相互绘制。我想做第一层,在上面画点东西,然后放第二层,然后画第三层。如何从我拥有的 UIView 子类中完成它?
这不是正确的答案。只要有可能,我们应该使用 CAlayer 而不是覆盖 drawRect 以避免潜在的性能问题。请参阅 Apple 的 WWDC 2012 视频 - iOS 应用程序性能:图形和动画(链接到站点 developer.apple.com/videos/wwdc/2012)。从 11.40 查看,Apple 工程师专门解决了这种情况。
Nathan,我评论了您的声明“始终使用 drawRect:”。这是不正确的。 Apple 建议使用 CALayer 或 drawLayer:inContext ,除非绝对需要覆盖 drawRect。详细信息在 WWDC 2012 视频中。您是正确的,通过覆盖 drawRect (但不是唯一的解决方案)可以轻松实现某些情况,例如矢量绘图。然而,在大多数情况下,CALayer 提供了优于 drawRect 的性能优势。因此,答案应该是“逐案”。【参考方案2】:
drawRect
只应在绝对需要时实施。 drawRect
的默认实现包括许多智能优化,例如智能缓存视图的渲染。覆盖它会绕过所有这些优化。那很糟。有效地使用图层绘制方法几乎总是优于自定义drawRect
。 Apple 经常使用UIView
作为CALayer
的代表——事实上,每个UIView is the delegate of it's layer。您可以在几个 Apple 示例中了解如何在 UIView 中自定义图层绘图,包括(目前)ZoomingPDFViewer。
虽然drawRect
的使用很常见,但至少从 IIRC 的 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
【讨论】:
这个答案与@NathanEror 接受的答案相矛盾。在我看来,要避免drawRect
的原因是每次刷新视图时都会调用它,即使没有真正改变;如果没有drawRect
,那么Core Animation 使用层的现有后备存储,这可能是之前绘制的。但我非常困惑,需要一个官方的 Apple 序列图......有任何指示吗?
参见 WWDC 2012 会议“iOS 应用性能:图形和动画”。这可能与您正在寻找的东西一样接近。 developer.apple.com/videos/wwdc/2012
天哪,这是一个很棒的演示文稿!我刚升级...我能感觉到我的生命值和法力值在增加!【参考方案3】:
这里是来自 Apple 的示例 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
...
【讨论】:
如果有人无法真正弄清楚,注释的意思是它可能会编译出整个 drawRect 方法,如果它定义但没有任何内容,所以它与不实现 drawRect 相同所有,他们只是插入了它,所以他们可以解释为什么你不使用它。对我来说有点困惑,但我注释掉了整个空的 drawRect 方法,并且程序仍然可以正常工作(ZoomingPDFViewer) No... 注释意味着 IF -drawRect: 存在,UIKit 的行为不同(即 UIKit 会查看视图是否响应到选择器 drawRect:,并且基于此,它的 CALayer 可能会或可能不会失效) .【参考方案4】:您是否使用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
在动画运行时不会返回正确的(即当前)值。相反,它返回最终值(即动画完成时的值)。
为了在动画期间获取当前值,您必须访问传递给drawLayer(_:inContext:)
的layer
参数的backgroundColor
。而且您还必须绘制到context
参数。
知道视图的self.layer
和传递给drawLayer(_:inContext:)
的layer
参数并不总是同一层非常重要!后者可能是前者的副本,其中部分动画已应用于其属性。 这样您就可以访问飞行中动画的正确属性值。
现在绘图按预期工作:
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.
【讨论】:
【参考方案5】:在 iOS 上,视图与其层之间的重叠非常大。默认情况下,视图是其层的委托,并实现层的drawLayer:inContext:
方法。据我了解,drawRect:
和 drawLayer:inContext:
在这种情况下或多或少是等效的。可能drawLayer:inContext:
的默认实现会调用drawRect:
,或者drawRect:
仅在您的子类未实现drawLayer:inContext:
时才会调用。
如何决定使用哪种方法?每个都有用例吗?
这并不重要。为了遵守约定,我通常会使用drawRect:
,而当我实际上必须绘制不属于视图的自定义子层时,我会保留使用drawLayer:inContext:
。
【讨论】:
你在几秒钟内就击败了我。 :-P @Nathan:但你的回答比我的好。 嗨 Ole,我对 iOS 中的自定义绘图非常陌生。我有一个子类 UIView 类,我需要在 3 个不同的图层上相互绘制。我想做第一层,在上面画点东西,然后放第二层,然后画第三层。如何从我拥有的 UIView 子类中完成它? 这两个函数有不同的用途。在下面查看我的答案:***.com/a/36050120/1183577【参考方案6】:Apple Documentation 有这样的说法:“还有其他方法可以提供视图的内容,例如直接设置底层的内容,但覆盖 drawRect: 方法是最常用的技术。”
但它没有涉及任何细节,所以这应该是一个线索:除非你真的想弄脏你的手,否则不要这样做。
UIView 层的委托指向 UIView。但是,根据是否实现 drawRect:,UIView 的行为会有所不同。例如,如果您直接在图层上设置属性(例如其背景颜色或角半径),如果您有一个 drawRect: 方法,这些值将被覆盖 - 即使它完全为空(即甚至不调用 super)。
【讨论】:
以上是关于iOS:使用 UIView 的 'drawRect:' 与其层的委托 'drawLayer:inContext:'的主要内容,如果未能解决你的问题,请参考以下文章