在iOS上,setNeedsDisplay真的不会导致drawRect被调用…除非CALayer的显示或drawInContext终于调用drawRect?
我不太了解CALayer的display
和drawInContext
如何与视图中的drawRect
相关联。
如果我有一个每隔1秒设置[self.view setNeedsDisplay]
的NSTimer,那么drawRect
每1秒调用一次,如drawRect
的NSLog语句所示。
但是,如果我inheritance一个CALayer并将其用于视图,如果我使display
方法为空,那么现在drawRect
永远不会被调用。 更新:但display
每1秒调用一次,如NSLog语句所示。
如果我删除那个空的display
方法并添加一个空的drawInContext
方法, drawRect
再也不会被调用。 更新:但是drawInContext
每隔1秒调用一次,如NSLog语句所示。
究竟发生了什么? 看起来display
可以select性地调用drawInContext
, drawInContext
可以select性地调用drawRect
(怎么样?),但是这里的真实情况是什么?
更新:有更多的线索回答:
我将CoolLayer.m
代码更改为以下内容:
-(void) display { NSLog(@"In CoolLayer's display method"); [super display]; } -(void) drawInContext:(CGContextRef)ctx { NSLog(@"In CoolLayer's drawInContext method"); [super drawInContext:ctx]; }
所以,如果在视图中的位置(100,100)处有一个月亮(如Core Graphics绘制的圆圈),现在我将其更改为位置(200,200),自然,我将调用[self.view setNeedsDisplay]
,现在,CALayer将根本没有caching新的视图图像,因为我的drawRect
指出现在应该如何显示月亮。
即使如此,入口点是CALayer的display
,然后CALayer的drawInContext
:如果我在drawRect
设置一个断点,调用堆栈显示:
所以我们可以看到CoolLayer的display
先input,然后进入CALayer的display
,然后是CoolLayer的drawInContext
,然后是CALayer的drawInContext
,即使在这种情况下,新图像也不存在这样的caching。
最后,CALayer的drawInContext
调用委托的drawLayer:InContext
。 委托是视图( drawLayer:InContext
或UIView)…和drawLayer:InContext
是UIView中的默认实现(因为我没有覆盖它)。 最后是drawLayer:InContext
调用drawRect
。
所以我猜两点:为什么它进入CALayer即使没有caching的图像? 因为通过这种机制,图像被绘制在上下文中,最后返回display
,CGImage从这个上下文创build,然后它被设置为新的图像caching。 这是CALayer如何caching图像。
另一件我不太确定的事情是:如果[self.view setNeedsDisplay]
总是触发drawRect
被调用,那么CALayer中的caching图像什么时候可以使用? 可能是…在Mac OS X上,当另一个窗口覆盖一个窗口,现在顶部窗口被移走。 现在我们不需要调用drawRect
来重绘所有东西,但是可以在CALayer中使用caching的图像。 或者在iOS上,如果我们停止应用程序,做一些其他的事情,并回到应用程序,那么可以使用caching的图像,而不是调用drawRect
。 但是如何区分这两种“脏”呢? 一个是“未知的脏” – 月亮需要根据drawRect
逻辑的指示重新绘制(它也可以使用随机数作为坐标)。 其他types的肮脏是它被掩盖或使消失,现在需要重新显示。
当一个图层需要显示并且没有有效的后备存储时(可能是因为图层收到了一个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:
中实现displayLayer:
必须以某种方式创build自己的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
操作没有logging。
-[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); }
UIView的更新过程基于dirty
状态,意味着如果外观没有变化,视图不可能被重画。
这是开发人员参考中提到的内部实现。
实现drawInContext或display或drawRect告诉操作系统当视图变脏(needsDisplay)时,你想调用哪一个。 select一个你想调用一个肮脏的视图,并实现,不要把任何代码,你依赖执行在其他人。