iOS:使用 UIView 的 'drawRect:' 与它层的委托 'drawLayer:inContext:'
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4979192/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
iOS: Using UIView's 'drawRect:' vs. its layer's delegate 'drawLayer:inContext:'
提问by Itamar Katz
I have a class which is a subclass of UIView
. I am able to draw stuff inside the view either by implementing the drawRect
method, or by implementing drawLayer:inContext:
which is a delegate method of CALayer
.
我有一个类,它是UIView
. 我能够通过实现绘制的东西视图内任一drawRect
方法,或通过实施drawLayer:inContext:
这是一个委托方法CALayer
。
I have two questions:
我有两个问题:
- How to decide which approach to use? Is there a use case for each one?
If I implement
drawLayer:inContext:
, it is called (anddrawRect
isn't, at least as far as putting a breakpoint can tell), even if I don't assign my view as theCALayer
delegate by using:[[self layer] setDelegate:self];
how come the delegate method is called if my instance is not defined to be the layer's delegate? and what mechanism prevents
drawRect
from being called ifdrawLayer:inContext:
is called?
- 如何决定使用哪种方法?每个都有用例吗?
如果我实现
drawLayer:inContext:
,它会被调用(并且drawRect
不是,至少就放置断点而言是这样),即使我没有使用以下方法将我的视图分配为CALayer
委托:[[self layer] setDelegate:self];
如果我的实例没有定义为层的委托,怎么会调用委托方法?
drawRect
如果drawLayer:inContext:
被调用,什么机制可以防止被调用?
采纳答案by Nathan Eror
How to decide which approach to use? Is there a use case for each one?
如何决定使用哪种方法?每个都有用例吗?
Always use drawRect:
, and never use a UIView
as the drawing delegate for any CALayer
.
始终使用drawRect:
,永远不要使用 aUIView
作为 any 的绘图委托CALayer
。
how come the delegate method is called if my instance is not defined to be the layer's delegate? and what mechanism prevents drawRect from being called if
drawLayer:inContext:
is called?
如果我的实例未定义为层的委托,那么如何调用委托方法?如果
drawLayer:inContext:
被调用,什么机制可以防止 drawRect 被调用?
Every UIView
instance is the drawing delegate for its backing CALayer
. That's why [[self layer] setDelegate:self];
seemed to do nothing. It's redundant. The drawRect:
method is effectively the drawing delegate method for the view's layer. Internally, UIView
implements drawLayer:inContext:
where it does some of its own stuff and then calls drawRect:
. You can see it in the debugger:
每个UIView
实例都是其支持的绘图委托CALayer
。这就是为什么[[self layer] setDelegate:self];
似乎什么都不做。这是多余的。该drawRect:
方法实际上是视图层的绘制委托方法。在内部,UIView
实现drawLayer:inContext:
它自己做一些事情的地方,然后调用drawRect:
. 您可以在调试器中看到它:
This is why drawRect:
was never called when you implemented drawLayer:inContext:
. It's also why you should never implement any of the CALayer
drawing delegate methods in a custom UIView
subclass. You should also never make any view the drawing delegate for another layer. That will cause all sorts of wackiness.
这就是为什么drawRect:
在您实现drawLayer:inContext:
. 这也是您永远不应该CALayer
在自定义UIView
子类中实现任何绘图委托方法的原因。您也不应该将任何视图作为另一层的绘图委托。这会导致各种古怪。
If you are implementing drawLayer:inContext:
because you need to access the CGContextRef
, you can get that from inside of your drawRect:
by calling UIGraphicsGetCurrentContext()
.
如果要实现drawLayer:inContext:
,因为你需要访问CGContextRef
,你可以从你的内部drawRect:
调用UIGraphicsGetCurrentContext()
。
回答by quellish
drawRect
should only be implemented when absolutely needed. The default implementation of drawRect
includes a number of smart optimizations, like intelligently caching the view's rendering. Overriding it circumvents all of those optimizations. That's bad. Using the layer drawing methods effectively will almost always outperform a custom drawRect
. Apple uses a UIView
as the delegate for a CALayer
often - in fact, every UIView is the delegate of it's layer. You can see how to customize the layer drawing inside a UIView in several Apple samples including (at this time) ZoomingPDFViewer.
drawRect
只有在绝对需要时才应该实施。的默认实现drawRect
包括许多智能优化,例如智能缓存视图的渲染。覆盖它可以绕过所有这些优化。那很糟。有效地使用图层绘制方法几乎总是优于自定义drawRect
. Apple经常使用 aUIView
作为a的委托CALayer
——事实上,每个UIView 都是它的 layer 的委托。您可以在几个 Apple 示例中看到如何在 UIView 中自定义图层绘制,包括(此时)ZoomingPDFViewer。
While the use of drawRect
is common, it's a practice that has been discouraged since at least 2002/2003, IIRC. There aren't many good reasons left to go down that path.
虽然使用drawRect
很常见,但至少自 2002/2003 年以来,IIRC 一直不鼓励这种做法。走这条路的理由并不多。
Advanced Performance Optimization on iPhone OS(slide 15)
iPhone OS 上的高级性能优化(幻灯片 15)
Technical Q&A QA1708: Improving Image Drawing Performance on iOS
回答by Dennis Fan
Here're codes of Sample ZoomingPDFViewer from Apple:
以下是 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
{
...
}
回答by fluidsonic
Whether you use drawLayer(_:inContext:)
or drawRect(_:)
(or both) for custom drawing code depends on whether you need access to the current value of a layer property while it is being animated.
是否使用drawLayer(_:inContext:)
或drawRect(_:)
(或两者)用于自定义绘图代码取决于您是否需要在设置动画时访问图层属性的当前值。
I was struggling today with various rendering issues related to these two functions when implementing my own Label class. After checking out the documentation, doing some trial-and-error, decompiling UIKit and inspecting Apple's Custom Animatable Properties exampleI got a good sense on how it's working.
我今天挣扎实施时涉及到这两个功能不同的渲染问题,我自己的Label类。在查看文档、反复试验、反编译 UIKit 并检查 Apple 的自定义动画属性示例后,我对它的工作原理有了很好的了解。
drawRect(_:)
drawRect(_:)
If you don't need to access the current value of a layer/view property during its animation you can simply use drawRect(_:)
to perform your custom drawing. Everything will work just fine.
如果您不需要在动画期间访问图层/视图属性的当前值,您可以简单地使用它drawRect(_:)
来执行您的自定义绘图。一切都会好起来的。
override func drawRect(rect: CGRect) {
// your custom drawing code
}
drawLayer(_:inContext:)
drawLayer(_:inContext:)
Let's say for example you want to use backgroundColor
in your custom drawing code:
例如,假设您想backgroundColor
在自定义绘图代码中使用:
override func drawRect(rect: CGRect) {
let colorForCustomDrawing = self.layer.backgroundColor
// your custom drawing code
}
When you test your code you'll notice that backgroundColor
does not return the correct (i.e. the current) value while an animation is in-flight. Instead it returns the final value (i.e. the value for when the animation is completed).
当您测试您的代码时,您会注意到backgroundColor
在动画进行中时不会返回正确的(即当前)值。相反,它返回最终值(即动画完成时的值)。
In order to get the currentvalue during the animation, you must access the backgroundColor
of the layer
parameterpassed to drawLayer(_:inContext:)
. And you must also draw to the context
parameter.
为了在动画期间获取当前值,您必须访问传递给backgroundColor
的layer
参数的drawLayer(_:inContext:)
。并且您还必须绘制到context
参数。
It is very important to know that a view's self.layer
and the layer
parameter passed to drawLayer(_:inContext:)
are not always the same layer! The latter might be a copy of the former with partial animations already applied to its properties.That way you can access correct property values of in-flight animations.
非常重要的是要知道视图self.layer
和layer
传递给的参数drawLayer(_:inContext:)
并不总是同一层!后者可能是前者的副本,其中部分动画已经应用于其属性。这样您就可以访问飞行中动画的正确属性值。
Now the drawing works as expected:
现在绘图按预期工作:
override func drawLayer(layer: CALayer, inContext context: CGContext) {
let colorForCustomDrawing = layer.backgroundColor
// your custom drawing code
}
But there are two new issue: setNeedsDisplay()
and several properties like backgroundColor
and opaque
do no longer work for your view. UIView
does no longer forward calls and changes to its own layer.
但有两个新问题:setNeedsDisplay()
喜欢和几个属性backgroundColor
和opaque
做不再工作,为您的视图。UIView
不再将调用和更改转发到自己的层。
setNeedsDisplay()
does only do something if your view implements drawRect(_:)
. It doesn't matter if the function actually does something but UIKit uses it to determine whether you do custom drawing or not.
setNeedsDisplay()
仅当您的视图实现时才执行某些操作drawRect(_:)
。该函数是否实际执行某些操作并不重要,但 UIKit 使用它来确定您是否进行自定义绘图。
The properties likely don't work anymore because UIView
's own implementation of drawLayer(_:inContext:)
is no longer called.
这些属性可能不再起作用,因为不再调用UIView
自己的实现drawLayer(_:inContext:)
。
So the solution is quite simple. Just call the superclass' implementation of drawLayer(_:inContext:)
and implement an empty drawRect(_:)
:
所以解决方法很简单。只需调用超类的实现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.
}
Summary
概括
Use drawRect(_:)
as long as you don't have the problem that properties return wrong values during an animation:
使用drawRect(_:)
只要你没有这样的属性在动画过程中返回错误值的问题:
override func drawRect(rect: CGRect) {
// your custom drawing code
}
Use drawLayer(_:inContext:)
anddrawRect(_:)
if you need to access the current value of view/layer properties while they are being animated:
如果您需要在动画制作时访问视图/图层属性的当前值,请使用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.
}
回答by Ole Begemann
On iOS, the overlap between a view and its layer is very large. By default, the view is the delegate of its layer and implements the layer's drawLayer:inContext:
method. As I understand it, drawRect:
and drawLayer:inContext:
are more or less equivalent in this case. Possibly, the default implementation of drawLayer:inContext:
calls drawRect:
, or drawRect:
is only called if drawLayer:inContext:
is not implemented by your subclass.
在 iOS 上,视图与其层之间的重叠非常大。默认情况下,视图是其层的委托并实现层的drawLayer:inContext:
方法。根据我的理解,drawRect:
并drawLayer:inContext:
在这种情况下或多或少等同。可能是drawLayer:inContext:
调用的默认实现drawRect:
,或者drawRect:
仅drawLayer:inContext:
在您的子类未实现时才调用。
How to decide which approach to use? Is there a use case for each one?
如何决定使用哪种方法?每个都有用例吗?
It doesn't really matter. To follow the convention, I would normally use drawRect:
and reserve the use of drawLayer:inContext:
when I actually have to draw custom sublayers that are not part of a view.
这并不重要。为了遵循惯例,我通常会在实际必须绘制不属于视图的自定义子图层时使用drawRect:
并保留使用drawLayer:inContext:
。
回答by jamie
The Apple Documentationhas this to say: "There are also other ways to provide a view's content, such as setting the contents of the underlying layer directly, but overriding the drawRect: method is the most common technique."
在苹果的文档有这样一段话:“还有其他的方式来提供视图的内容,如直接设置在底层的内容,但重写的drawRect:方法是最常用的技术”
But it doesn't go into any details, so that should be a clue: don't do it unless you really want to get your hands dirty.
但它没有涉及任何细节,所以这应该是一个线索:除非你真的想弄脏你的手,否则不要这样做。
The UIView's layer's delegate is pointed at the UIView. However, the UIView does behave differently depending on whether or not drawRect: is implemented. For example, if you set the properties on the layer directly (such as its background color or its corner radius), these values are overwritten if you have a drawRect: method - even if its completely empty (i.e. doesnt even call super).
UIView 的层的委托指向 UIView。但是,UIView 的行为会根据是否实现 drawRect: 而有所不同。例如,如果你直接在图层上设置属性(例如它的背景颜色或它的角半径),如果你有一个 drawRect: 方法,这些值会被覆盖——即使它完全为空(即甚至不调用 super)。
回答by Singer Quincy
For layer-backed views with custom content, you should continue to override the view's methods to do your drawing. A layer-backed view automatically makes itself the delegate of its layer and implements the needed delegate methods, and you should not change that configuration. Instead, you should implement your view's drawRect: method to draw your content.Core Animation Programming Guide
对于具有自定义内容的图层支持视图,您应该继续覆盖视图的方法来进行绘图。层支持的视图会自动使自己成为其层的委托并实现所需的委托方法,您不应更改该配置。相反,您应该实现视图的 drawRect: 方法来绘制您的内容。核心动画编程指南