I don't really understand how CALayer's display
and drawInContext
relate to drawRect
in the view.
If I have an NSTimer that sets the [self.view setNeedsDisplay]
every 1 second, then drawRect
is called every 1 second, as shown by an NSLog statement inside of drawRect
.
But if I subclass a CALayer and use that for the view, if I make the display
method empty, then now drawRect
is never called. Update: But display
is called every 1 second, as shown by an NSLog statement.
If I remove that empty display
method and add an empty drawInContext
method, again, drawRect
is never called. Update: But drawInContext
is called every 1 second, as shown by an NSLog statement.
What is exactly happening? It seems that display
can selectively call drawInContext
and drawInContext
can selectively somehow call drawRect
(how?), but what is the real situation here?
Update: there is more clue to the answer:
I changed the CoolLayer.m
code to the following:
-(void) display {
NSLog(@"In CoolLayer's display method");
[super display];
}
-(void) drawInContext:(CGContextRef)ctx {
NSLog(@"In CoolLayer's drawInContext method");
[super drawInContext:ctx];
}
So, let's say, if there is a moon (as a circle drawn by Core Graphics) at location (100,100) in the View, and now I change it to location (200,200), naturally, I will call [self.view setNeedsDisplay]
, and now, CALayer will have no cache at all for the new view image, as my drawRect
dictates how the moon should now be displayed.
Even so, the entry point is CALayer's display
, and then CALayer's drawInContext
: If I set a break point at drawRect
, the call stack shows:
So we can see that CoolLayer's display
is entered first, and it goes to CALayer's display
, and then CoolLayer's drawInContext
, and then CALayer's drawInContext
, even though in this situation, no such cache exist for the new image.
Then finally, CALayer's drawInContext
calls the delegate's drawLayer:InContext
. The delegate is the view (FooView or UIView)... and drawLayer:InContext
is the default implementation in UIView (as I did not override it). It is finally that drawLayer:InContext
calls drawRect
.
So I am guessing two points: why does it enter CALayer even though there is no cache for the image? Because through this mechanism, the image is drawn in the context, and finally returns to display
, and the CGImage is created from this context, and then it is now set as the new image cached. This is how CALayer caches images.
Another thing I am not quite sure is: if [self.view setNeedsDisplay]
always trigger drawRect
to be called, then when can a cached image in CALayer be used? Could it be... on Mac OS X, when another window covers up a window, and now the top window is moved away. Now we don't need to call drawRect
to redraw everything, but can use the cached image in the CALayer. Or on iOS, if we stop the app, do something else, and come back to the app, then the cached image can be used, instead of calling drawRect
. But how to distinguish these two types of "dirty"? One is a "unknown dirty" -- that the moon needs to be redrawn as dictated by the drawRect
logic (it can use a random number there for the coordinate too). The other types of dirty is that it was covered up or made to disappear, and now needs to be re-shown.
The update procedure of UIView is based on the
dirty
state meaning the view is not likely to be redrawn if there's no change at it's appearance.That is internal implementation mentioned at the developer reference.
Implementing a drawInContext or display or drawRect tells the OS which one you want called when the view is dirty (needsDisplay). Pick the one you want called for a dirty view and implement that, and don't put any code you depend on getting executed in the others.
When a layer needs to be displayed and has no valid backing store (perhaps because the layer received a
setNeedsDisplay
message), the system sends thedisplay
message to the layer.The
-[CALayer display]
method looks roughly like this:So, if you override
display
, none of that happens unless you call[super display]
. And if you implementdisplayLayer:
inFooView
, you have to create your ownCGImage
somehow and store it in the layer'scontents
property.The
-[CALayer drawInContext:]
method looks roughly like this:The
onDraw
action is not documented as far as I know.The
-[UIView drawLayer:inContext:]
method looks roughly like this: