drawRect中上下文和线程之间的问题
我正在尝试在 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 UIImage
s 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
当你开始绘制图像时,上下文已经消失,你说得非常正确。 UIKit 在调用
drawRect:
之前为您的视图设置一个绘图上下文,然后将其销毁。作为一般规则,只有并且始终会在从框架类继承的draw...
方法中为您设置这样的上下文。那么你能做什么呢?实际上应该相当容易。事实上,Anomie的答案已经告诉你了。您需要做的就是在
drawContentView:
中调用图像上的绘制方法,您知道您有一个上下文。在loadPreview
方法中,您调用setNeedsDisplay
*,并在drawContentView:
中检查preview
是否为nil
,如果没有则绘制。只是为了扩展 Anomie 的答案,代码应如下所示:您可能还可以将
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 adraw...
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 theloadPreview
method, you callsetNeedsDisplay
*, and indrawContentView:
you check ifpreview
isnil
, drawing if not. Just to expand on Anomie's answer, here's what the code should look like: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:
, becausesetNeedsDisplay
doesn't calldrawRect:
directly, it just sets a flag.最直接的方法是完全不要乱用
drawRect:
,而是将图像放入 UIImageViews 中。苹果声称,即使是空的drawRect:
也会改变某些东西,从而使渲染速度慢得多。但如果必须的话,您必须将图像保存到类中的 ivars 中(不要忘记保留它们),调用
setNeedsDisplay
或setNeedsDisplayInRect:
(在主线程,因此您需要使用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 emptydrawRect:
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
orsetNeedsDisplayInRect:
(on the main thread, so you'll need to useperformSelector:withObject:onMainThread:
if you aren't already) to inform the system that it needs to call yourdrawRect:
again, and then whendrawRect:
is called again use the saved images to draw.