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

3
我正在尝试在UIView中手动使用drawRect:绘制几个UIImage,这些图片是从网上动态下载的,因此我创建了一个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...],因为我不再在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:中。有什么提示吗?


你能贴出 drawRect: 方法的片段吗?你是在 drawRect: 中创建 NSOperation 然后尝试从另一个线程中绘制图像吗? - jscs
我现在不在家,一回来就会更新我的问题并附上代码片段。谢谢! - ferostar
刚刚更新了带有代码片段的文章。 - ferostar
2个回答

3
您的观点十分正确,当您开始绘制图像时,上下文已经消失了。在调用drawRect:之前,UIKit会为您的视图设置一个绘图上下文,并在之后将其销毁。一般规则是,只有从框架类继承而来的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的观点可能是错误的,因为setNeedsDisplay方法不会直接调用drawRect:方法,而只是设置一个标志。


2
实现此功能最直接的方法是根本不要碰drawRect:,而是将图像放在UIImageViews中。苹果声称,即使是一个空的drawRect:也会改变某些东西,从而使渲染速度变慢。
但是如果必须这样做,你需要在类中保存图像到ivars(不要忘记retain它们),调用setNeedsDisplaysetNeedsDisplayInRect:(在主线程上,所以如果没有使用,你需要使用performSelector:withObject:onMainThread:)来通知系统需要再次调用drawRect:,然后当drawRect:再次被调用时,使用保存的图像进行绘制。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接