我真的不明白CALayer display
和drawInContext
在视图中与drawRect
的关系。
如果我有一个NSTimer每1秒设置[self.view setNeedsDisplay]
,则每1秒调用drawRect
,如drawRect
内的NSLog语句所示。
但是如果我将CALayer子类化并将其用于视图,如果我将display
方法设为空,那么现在永远不会调用drawRect
。 更新:但是{1}每1秒调用一次,如NSLog语句所示。
如果我删除该空display
方法并添加空display
方法,则永远不会调用drawInContext
。 更新:但是{1}每1秒调用一次,如NSLog语句所示。
究竟发生了什么?似乎drawRect
可以选择性地调用drawInContext
而display
可以选择性地调用drawInContext
(如何?),但这里的真实情况是什么?
更新:答案有更多线索:
我将drawInContext
代码更改为以下内容:
drawRect
所以,让我们说,如果在视图中的位置(100,100)有月亮(由Core Graphics绘制的圆圈),现在我将其更改为位置(200,200),我自然会调用{{1现在,CALayer对于新的视图图像根本没有缓存,因为我的CoolLayer.m
指示了现在应该如何显示月亮。
即便如此,入口点是CALayer的-(void) display {
NSLog(@"In CoolLayer's display method");
[super display];
}
-(void) drawInContext:(CGContextRef)ctx {
NSLog(@"In CoolLayer's drawInContext method");
[super drawInContext:ctx];
}
,然后是CALayer的[self.view setNeedsDisplay]
:如果我在drawRect
设置了一个断点,则调用堆栈会显示:
所以我们可以看到CoolLayer的display
首先被输入,然后进入CALayer的drawInContext
,然后是CoolLayer的drawRect
,然后是CALayer的display
,即使在在这种情况下,新图像不存在这样的缓存。
最后,CALayer的display
调用代理人的drawInContext
。委托是视图(FooView或UIView)...而drawInContext
是UIView中的默认实现(因为我没有覆盖它)。最后,drawInContext
调用drawLayer:InContext
。
所以我猜两点:即使图像没有缓存,为什么它会进入CALayer?因为通过这种机制,图像在上下文中绘制,最后返回到drawLayer:InContext
,CGImage是从这个上下文创建的,然后它现在被设置为缓存的新图像。这就是CALayer缓存图像的方式。
我不太确定的另一件事是:如果drawLayer:InContext
始终触发drawRect
被调用,那么何时可以使用CALayer中的缓存图像?可能是......在Mac OS X上,当另一个窗口遮住窗口时,现在顶部的窗口被移开了。现在我们不需要调用display
来重绘所有内容,但可以使用CALayer中的缓存图像。或者在iOS上,如果我们停止应用程序,执行其他操作,然后返回应用程序,则可以使用缓存的图像,而不是调用[self.view setNeedsDisplay]
。但如何区分这两种“脏”?一个是“未知的脏” - 月亮需要按照drawRect
逻辑的指示重新绘制(它也可以在那里使用随机数作为坐标)。其他类型的脏被掩盖或消失,现在需要重新显示。
答案 0 :(得分:12)
当需要显示某个图层并且没有有效的后备存储时(可能是因为该图层收到了setNeedsDisplay
消息),系统会向该图层发送display
消息。
-[CALayer display]
方法看起来大致如下:
- (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;
}
因此,如果您覆盖display
,除非您致电[super display]
,否则不会发生这种情况。如果您在displayLayer:
中实施FooView
,则必须以某种方式创建自己的CGImage
并将其存储在图层的contents
属性中。
-[CALayer drawInContext:]
方法看起来大致如下:
- (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];
}
}
}
据我所知,onDraw
行动没有记录。
-[UIView drawLayer:inContext:]
方法看起来大致如下:
- (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);
}
答案 1 :(得分:1)
UIView的更新过程基于dirty
状态,这意味着如果视图的外观没有变化,则视图不可能重绘。
这是开发人员参考中提到的内部实现。
答案 2 :(得分:0)
实现drawInContext或display或drawRect告诉操作系统在视图脏时需要调用哪一个(needsDisplay)。选择你想要调用脏视图的那个并实现它,并且不要让你依赖的任何代码在其他代码中执行。