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
.
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?
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:
anddrawLayer:inContext:
are more or less equivalent in this case. Possibly, the default implementation ofdrawLayer:inContext:
callsdrawRect:
, ordrawRect:
is only called ifdrawLayer:inContext:
is not implemented by your subclass.It doesn't really matter. To follow the convention, I would normally use
drawRect:
and reserve the use ofdrawLayer:inContext:
when I actually have to draw custom sublayers that are not part of a view.Whether you use
drawLayer(_:inContext:)
ordrawRect(_:)
(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.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 example I got a good sense on how it's working.
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.drawLayer(_:inContext:)
Let's say for example you want to use
backgroundColor
in 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).In order to get the current value during the animation, you must access the
backgroundColor
of thelayer
parameter passed todrawLayer(_:inContext:)
. And you must also draw to thecontext
parameter.It is very important to know that a view's
self.layer
and thelayer
parameter passed todrawLayer(_: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.Now the drawing works as expected:
But there are two new issue:
setNeedsDisplay()
and several properties likebackgroundColor
andopaque
do no longer work for your view.UIView
does no longer forward calls and changes to its own layer.setNeedsDisplay()
does only do something if your view implementsdrawRect(_:)
. It doesn't matter if the function actually does something but UIKit uses it to determine whether you do custom drawing or not.The properties likely don't work anymore because
UIView
's own implementation ofdrawLayer(_:inContext:)
is no longer called.So the solution is quite simple. Just call the superclass' implementation of
drawLayer(_:inContext:)
and implement an emptydrawRect(_:)
:Summary
Use
drawRect(_:)
as long as you don't have the problem that properties return wrong values during an animation:Use
drawLayer(_:inContext:)
anddrawRect(_:)
if you need to access the current value of view/layer properties while they are being animated:drawRect
should only be implemented when absolutely needed. The default implementation ofdrawRect
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 customdrawRect
. Apple uses aUIView
as the delegate for aCALayer
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.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.Advanced Performance Optimization on iPhone OS (slide 15)
Core Animation Essentials
Understanding UIKit Rendering
Technical Q&A QA1708: Improving Image Drawing Performance on iOS
View Programming Guide: Optimizing View Drawing
Here're codes of Sample ZoomingPDFViewer from Apple:
Always use
drawRect:
, and never use aUIView
as the drawing delegate for anyCALayer
.Every
UIView
instance is the drawing delegate for its backingCALayer
. That's why[[self layer] setDelegate:self];
seemed to do nothing. It's redundant. ThedrawRect:
method is effectively the drawing delegate method for the view's layer. Internally,UIView
implementsdrawLayer:inContext:
where it does some of its own stuff and then callsdrawRect:
. You can see it in the debugger:This is why
drawRect:
was never called when you implementeddrawLayer:inContext:
. It's also why you should never implement any of theCALayer
drawing delegate methods in a customUIView
subclass. You should also never make any view the drawing delegate for another layer. That will cause all sorts of wackiness.If you are implementing
drawLayer:inContext:
because you need to access theCGContextRef
, you can get that from inside of yourdrawRect:
by callingUIGraphicsGetCurrentContext()
.The Apple Documentation has 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."
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).