IOS: 使用 UIView 的‘ draRect:’和它的图层代表‘ draLayer: inContext:’

我有一个类,它是 UIView的一个子类。我能够通过实现 drawRect方法,或者通过实现 drawLayer:inContext:(CALayer的一个委托方法) ,在视图内部绘制东西。

我有两个问题:

  1. 如何决定使用哪种方法? 每种方法都有用例吗?
  2. 如果我实现了 drawLayer:inContext:,它就会被调用(至少在放置断点的情况下,drawRect不会被调用) ,即使我没有使用以下方法将视图分配为 CALayer委托:

    [[self layer] setDelegate:self];

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

38386 次浏览

在 iOS 上,视图与其层之间的重叠非常大。默认情况下,视图是其层的委托,并实现该层的 drawLayer:inContext:方法。据我所知,drawRect:drawLayer:inContext:在这种情况下或多或少是等价的。可能的情况是,drawLayer:inContext:的默认实现调用 drawRect:,或者仅当子类没有实现 drawLayer:inContext:时才调用 drawRect:

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

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

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

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

如果我的实例没有被定义为层的委托,为什么委托方法被调用?如果 drawLayer:inContext:被调用,是什么机制阻止了 draRect 被调用?

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

drawRect: stacktrace

这就是为什么在实现 drawLayer:inContext:时从未调用 drawRect:的原因。这也是为什么您永远不应该在自定义 UIView子类中实现任何 CALayer绘图委托方法的原因。您也不应该为另一层制作任何绘图委托视图。那会引起各种各样的怪事。

如果您正在实现 drawLayer:inContext:,因为您需要访问 CGContextRef,您可以通过调用 UIGraphicsGetCurrentContext()drawRect:内部获得它。

苹果文档这样说: “还有其他提供视图内容的方法,比如直接设置底层的内容,但是覆盖 draRect: method 是最常用的技术。”

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

UIView 层的委托指向 UIView。但是,UIView 的行为确实有所不同,这取决于 draRect: 是否实现。例如,如果你直接在图层上设置属性(比如它的背景颜色或者它的角半径) ,如果你有一个 draRect: 方法,这些值会被覆盖-即使它是完全空的(即不调用 super)。

以下是来自苹果公司的缩放 PDFViewer 代码:

-(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。苹果经常使用 UIView作为 CALayer的代表——事实上,每个 UIView 是其层的委托都是如此。您可以在几个 Apple 示例中看到如何自定义 UIView 中的图层绘制,包括(此时) ZoomingPDFViewer。

虽然 drawRect的使用很普遍,但至少从2002/2003年开始就不鼓励这种做法,IIRC。没有多少好的理由让我们走这条路了。

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

核心动画要点

理解 UIKit 渲染

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

视图编程指南: 优化视图绘图

对于自定义绘图代码,是否使用 drawLayer(_:inContext:)drawRect(_:)(或两者都使用)取决于是否需要访问图层属性的当前值,而该图层属性正在进行动画处理。

我今天在实现 我自己的 Label 类时遇到了与这两个函数相关的各种呈现问题。在查看了文档、进行了一些试错、反编译了 UIKit 并检查了苹果的 自定义动画属性示例之后,我对它的工作方式有了很好的了解。

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()和一些属性(如 backgroundColoropaque)不再适用于您的视图。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(_:):

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.
}