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.
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.
override func drawRect(rect: CGRect) {
// your custom drawing code
}
drawLayer(_:inContext:)
Let's say for example you want to use backgroundColor
in your custom drawing code:
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).
In order to get the current value during the animation, you must access the backgroundColor
of the layer
parameter passed to drawLayer(_:inContext:)
. And you must also draw to the context
parameter.
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.
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()
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.
The properties likely don't work anymore because UIView
's own implementation of drawLayer(_:inContext:)
is no longer called.
So the solution is quite simple. Just call the superclass' implementation of drawLayer(_:inContext:)
and implement an empty 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:
override func drawRect(rect: CGRect) {
// your custom drawing code
}
Use drawLayer(_:inContext:)
and drawRect(_:)
if you need to access the current value of view/layer properties while they are being animated:
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.
}