On iOS, setNeedsDisplay really doesn't cause drawRect to be called... unless CALayer's display or drawInContext finally calls drawRect?
calayer, core-animation, drawrect, ios, uiview
Solution
When a layer needs to be displayed and has no valid backing store (perhaps because the layer received a `setNeedsDisplay` message), the system sends the `display` message to the layer.
The `-[CALayer display]` method looks roughly like this:
- (void)display {
if ([self.delegate respondsToSelector:@selector(displayLayer:)]) {
[[self.delegate retain] displayLayer:self];
[self.delegate release];
return;
}
CABackingStoreRef backing = _backingStore;
if (!backing) {
backing = _backingStore = ... code here to create and configure
the CABackingStore properly, given the layer size, isOpaque,
contentScale, etc.
}
CGContextRef gc = ... code here to create a CGContext that draws into backing,
with the proper clip region
... also code to set up a bitmap in memory shared with the WindowServer process
[self drawInContext:gc];
self.contents = backing;
}
So, if you override `display`, none of that happens unless you call `[super display]`. And if you implement `displayLayer:` in `FooView`, you have to create your own `CGImage` somehow and store it in the layer's `contents` property.
The `-[CALayer drawInContext:]` method looks roughly like this:
- (void)drawInContext:(CGContextRef)gc {
if ([self.delegate respondsToSelector:@selector(drawLayer:inContext:)]) {
[[self.delegate retain] drawLayer:self inContext:gc];
[self.delegate release];
return;
} else {
CAAction *action = [self actionForKey:@"onDraw"];
if (action) {
NSDictionary *args = [NSDictionary dictionaryWithObject:gc forKey:@"context"];
[action runActionForKey:@"onDraw" object:self arguments:args];
}
}
}
The `onDraw` action is not documented as far as I know.
The `-[UIView drawLayer:inContext:]` method looks roughly like this:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)gc {
set gc's stroke and fill color spaces to device RGB;
UIGraphicsPushContext(gc);
fill gc with the view's background color;
if ([self respondsToSelector:@selector(drawRect:)]) {
[self drawRect:CGContextGetClipBoundingBox(gc)];
}
UIGraphicsPopContext(gc);
}
Problem
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.