在iOS上,setNeedsDisplay确实不会导致调用drawRect ...除非CALayer的display或drawInContext最终调用drawRect?

时间:2012-05-28 22:26:15

标签: ios uiview core-animation calayer drawrect

我真的不明白CALayer displaydrawInContext在视图中与drawRect的关系。

如果我有一个NSTimer每1秒设置[self.view setNeedsDisplay],则每1秒调用drawRect,如drawRect内的NSLog语句所示。

但是如果我将CALayer子类化并将其用于视图,如果我将display方法设为空,那么现在永远不会调用drawRect更新:但是{1}每1秒调用一次,如NSLog语句所示。

如果我删除该空display方法并添加空display方法,则永远不会调用drawInContext更新:但是{1}每1秒调用一次,如NSLog语句所示。

究竟发生了什么?似乎drawRect可以选择性地调用drawInContextdisplay可以选择性地调用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设置了一个断点,则调用堆栈会显示:

enter image description here

所以我们可以看到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逻辑的指示重新绘制(它也可以在那里使用随机数作为坐标)。其他类型的脏被掩盖或消失,现在需要重新显示。

3 个答案:

答案 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)。选择你想要调用脏视图的那个并实现它,并且不要让你依赖的任何代码在其他代码中执行。