Problem between context and thread in 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(con开发者_JAVA技巧text, 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?
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.
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.
精彩评论