drawRect中上下文和线程之间的问题

发布于 2024-11-07 15:39:10 字数 2210 浏览 0 评论 0原文

我正在尝试在 UIView 中绘制几个 UIImage,并且使用 drawRect: 手动完成。鉴于图像是从网络动态下载的,我创建一个 NSOperation 并从第二个线程执行图像加载代码,以保持 UI 响应。因此,一旦图像被下载,它们就会出现在屏幕上。

但是......对于每个图像,我都会遇到以下错误:

<Error>: CGContextSaveGState: invalid context 0x0
<Error>: CGContextSetBlendMode: invalid context 0x0
<Error>: CGContextSetAlpha: invalid context 0x0
<Error>: CGContextTranslateCTM: invalid context 0x0
<Error>: CGContextScaleCTM: invalid context 0x0
<Error>: CGContextDrawImage: invalid context 0x0
<Error>: CGContextRestoreGState: invalid context 0x0

对于我所看到的,这意味着我试图在其中创建 [image drawInRect...]nil,因为我不再在 drawRect: 中了。

尝试这样做

UIGraphicsPushContext(UIGraphicsGetCurrentContext());

在绘制图像之前 ,但没有任何改变。我怎样才能克服这个问题?多线程是响应能力所必需的,但上下文却会因此而丢失。

更新:这是我的代码:

- (void)drawContentView:(CGRect)r
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    ....
    CGContextFillRect(context, r);
    UIApplication* app = [UIApplication sharedApplication];
        app.networkActivityIndicatorVisible = YES;


    NSOperationQueue *queue = [NSOperationQueue new];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]
                                            initWithTarget:self 
                                            selector:@selector(loadPreview) 
                                            object:nil];
    [queue addOperation:operation];
    [operation release];
}

然后

-(void)loadPreview
{
    self.preview = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString:self.picStringPreview]]];
    [self.preview drawInRect:CGRectMake(256, 18, 80, 65)];
}

我尝试在 loadPreview 中添加 UIGraphicsPushContext...,甚至执行 [self PerformSelectorOnMainThread...]那里,什么也没有。这是一个基于 Atebit 模型的自定义单元格,因此我的单元格的超类生成:

- (void)drawRect:(CGRect)r
{
    [(ABTableViewCell *)[self superview] drawContentView:r];
}

并且 drawContentView: 中的代码在超类的 drawRect: 中进行绘制。有什么提示吗?

I'm trying to draw a couple of UIImages in a UIView, and I'm doing it by hand with drawRect:. Given that the images are dynamically downloaded from the web, I create an NSOperation and I perform the image loading code from a second thread, in order to keep UI responsive. So once the images are downloaded, they appear on screen.

But... I get the following errors, for each of the images:

<Error>: CGContextSaveGState: invalid context 0x0
<Error>: CGContextSetBlendMode: invalid context 0x0
<Error>: CGContextSetAlpha: invalid context 0x0
<Error>: CGContextTranslateCTM: invalid context 0x0
<Error>: CGContextScaleCTM: invalid context 0x0
<Error>: CGContextDrawImage: invalid context 0x0
<Error>: CGContextRestoreGState: invalid context 0x0

For what I've seen, this means that the context in which I'm trying to make [image drawInRect...] is nil, because I'm not in drawRect: anymore.

Attempted doing

UIGraphicsPushContext(UIGraphicsGetCurrentContext());

before drawing the image but nothing changed. How can I overcome this thing? Multithreading is a must for responsiveness, and the context gets lost in the way.

UPDATE: Here's my code:

- (void)drawContentView:(CGRect)r
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    ....
    CGContextFillRect(context, r);
    UIApplication* app = [UIApplication sharedApplication];
        app.networkActivityIndicatorVisible = YES;


    NSOperationQueue *queue = [NSOperationQueue new];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]
                                            initWithTarget:self 
                                            selector:@selector(loadPreview) 
                                            object:nil];
    [queue addOperation:operation];
    [operation release];
}

And then

-(void)loadPreview
{
    self.preview = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString:self.picStringPreview]]];
    [self.preview drawInRect:CGRectMake(256, 18, 80, 65)];
}

I tried adding a UIGraphicsPushContext... in loadPreview, even doing a [self performSelectorOnMainThread...] there, and nothing. And this is a custom cell based on Atebit's model, so my cell's superclass makes:

- (void)drawRect:(CGRect)r
{
    [(ABTableViewCell *)[self superview] drawContentView:r];
}

and the code in drawContentView: gets draw'd in superclass's drawRect:. Any hint?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我的黑色迷你裙 2024-11-14 15:39:10

当你开始绘制图像时,上下文已经消失,你说得非常正确。 UIKit 在调用 drawRect: 之前为您的视图设置一个绘图上下文,然后将其销毁。作为一般规则,只有并且始终会在从框架类继承的 draw... 方法中为您设置这样的上下文。

那么你能做什么呢?实际上应该相当容易。事实上,Anomie的答案已经告诉你了。您需要做的就是在 drawContentView: 中调用图像上的绘制方法,您知道您有一个上下文。在 loadPreview 方法中,您调用 setNeedsDisplay*,并在 drawContentView: 中检查 preview 是否为 nil,如果没有则绘制。只是为了扩展 Anomie 的答案,代码应如下所示:

-(void)loadPreview
{
    self.preview = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString:self.picStringPreview]]];
    [self setNeedsDisplay];
}

- (void)drawContentView:(CGRect)r
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    ....
    CGContextFillRect(context, r);
    UIImage * prvw = self.preview;    // Avoid making two method calls
    if( prvw ){
        [prvw drawInRect:CGRectMake(256, 18, 80, 65)];
    }
    // etc.

您可能还可以将 NSOperation 的创建和分派移至绘图方法的之外。建议所有绘图方法执行其需要执行的操作并尽快终止。


*我认为 Anomie 需要通过 performSelector:...onMainThread: 调用它可能是错误的,因为 setNeedsDisplay 不会调用 drawRect:直接,它只是设置一个标志。

You're quite correct about the context being gone by the time you get to drawing the images. UIKit sets up a drawing context for your view before drawRect: is called, and it's destroyed afterwards. As a general rule, there will only, and always, be a context set up for you like this in a draw... method that you've inherited from a framework class.

So what can you do? It should actually be rather easy. Anomie's answer already told you, in fact. All you need to do is call the draw method on your image back in drawContentView: where you know you have a context. In the loadPreview method, you call setNeedsDisplay*, and in drawContentView: you check if preview is nil, drawing if not. Just to expand on Anomie's answer, here's what the code should look like:

-(void)loadPreview
{
    self.preview = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString:self.picStringPreview]]];
    [self setNeedsDisplay];
}

- (void)drawContentView:(CGRect)r
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    ....
    CGContextFillRect(context, r);
    UIImage * prvw = self.preview;    // Avoid making two method calls
    if( prvw ){
        [prvw drawInRect:CGRectMake(256, 18, 80, 65)];
    }
    // etc.

You would also probably do well to move the creation and dispatching of the NSOperation out of the drawing method. It's recommended that all drawing methods do only what they need to do and terminate as quickly as possible.


*I think Anomie might be wrong about needing to call this through performSelector:...onMainThread:, because setNeedsDisplay doesn't call drawRect: directly, it just sets a flag.

dawn曙光 2024-11-14 15:39:10

最直接的方法是完全不要乱用 drawRect:,而是将图像放入 UIImageViews 中。苹果声称,即使是空的 drawRect: 也会改变某些东西,从而使渲染速度慢得多。

但如果必须的话,您必须将图像保存到类中的 ivars 中(不要忘记保留它们),调用 setNeedsDisplaysetNeedsDisplayInRect: (在主线程,因此您需要使用 performSelector:withObject:onMainThread: (如果您还没有)通知系统需要再次调用您的 drawRect: ,然后当再次调用 drawRect: 时,使用保存的图像进行绘制。

The most straightforward way to do this is not to mess with drawRect: at all, and instead put your images in UIImageViews. Apple claims that even an empty drawRect: changes something-or-other that makes rendering much slower.

But if you must, you'll have to save the images to ivars in your class (don't forget to retain them), call setNeedsDisplay or setNeedsDisplayInRect: (on the main thread, so you'll need to use performSelector:withObject:onMainThread: if you aren't already) to inform the system that it needs to call your drawRect: again, and then when drawRect: is called again use the saved images to draw.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文