3

我真的不明白 CALayerdisplay和视图中的drawInContext关系drawRect

如果我有一个设置[self.view setNeedsDisplay]每 1 秒的 NSTimer,则每 1 秒drawRect调用一次,如drawRect.

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

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

到底发生了什么?似乎display可以选择性地调用drawInContext并且drawInContext可以选择性地以某种方式调用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];
}

因此,假设在 View 的位置 (100,100) 处有一个月亮(由 Core Graphics 绘制的圆圈),现在我将其更改为位置 (200,200),自然会调用[self.view setNeedsDisplay],现在,CALayer 将新视图图像根本没有缓存,因为我drawRect决定现在应该如何显示月亮。

即便如此,入口点是 CALayer's display,然后是 CALayer's drawInContext: 如果我在 处设置断点drawRect,调用堆栈显示:

在此处输入图像描述

所以我们可以看到 CoolLayer'sdisplay是先进入的,然后是 CALayer's display,然后是 CoolLayer's drawInContext,然后是 CALayer's drawInContext,即使在这种情况下,新图像不存在这样的缓存。

最后,CALayerdrawInContext调用委托的drawLayer:InContext. 委托是视图(FooView 或 UIView)......并且drawLayer:InContext是 UIView 中的默认实现(因为我没有覆盖它)。终于drawLayer:InContext叫了drawRect

所以我猜测两点:为什么即使没有图像缓存它也会进入CALayer?因为通过这种机制,图像是在上下文中绘制的,最后返回到display,CGImage就是从这个上下文中创建出来的,然后现在将其设置为缓存的新图像。这就是 CALayer 缓存图像的方式。

我不太确定的另一件事是:如果[self.view setNeedsDisplay]总是触发drawRect被调用,那么什么时候可以使用 CALayer 中的缓存图像?可能是......在Mac OS X上,当另一个窗口覆盖一个窗口时,现在顶部窗口被移开。现在我们不需要调用drawRect重绘一切,而是可以使用CALayer中的缓存图像。或者在 iOS 上,如果我们停止应用程序,执行其他操作,然后返回应用程序,则可以使用缓存的图像,而不是调用drawRect. 但是如何区分这两种“脏”呢?一个是“未知的脏”——月亮需要按照drawRect逻辑的要求重新绘制(它也可以在那里使用随机数作为坐标)。其他类型的脏是它被掩盖或消失,现在需要重新显示。

4

3 回答 3

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:in 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);
}
于 2012-05-29T20:06:21.760 回答
1

UIView 的更新过程是基于dirty状态的,这意味着如果视图的外观没有变化,则视图不太可能被重绘。

那是开发人员参考中提到的内部实现。

于 2012-05-28T22:30:11.883 回答
0

实现 drawInContext 或 display 或 drawRect 会告诉操作系统在视图脏时要调用哪个(需要显示)。选择一个你想要调用脏视图的代码并实现它,不要将任何你依赖的代码放在其他代码中。

于 2012-05-30T15:49:14.280 回答