Using GCD/asynchronous code in drawRect: - iphone

I'm trying to load an image from a url inside of drawRect:. Our image-loading code is a method that looks like this: - (void) loadImage:(NSURL*)url done:(void(^)(UIImage*))done;, which creates an asynchronous NSURLConnection and calls back with the image.
So, my code in drawRect: looks like this:
CGContextRef context = UIGraphicsGetCurrentContext()
[service loadImage:url done:^(UIImage * image){
CGContextDrawImage(context, frame, image.CGImage);
}];
Unfortunately, this doesn't work. The image is never drawn.
I've also tried using synchronous connections ([NSData dataWithContentsOfUrl:]), but it blocks the thread and slows things down unnecessarily. I don't want to use a UIImageView.
What is the correct way to do this? Thanks!

I think the correct way would either be to load the image beforehand or draw the image as soon as you receive it by either calling "setNeedsDisplayInRect:" or doing your own drawing handling in a separate method (iOS might discourage you from doing this though, I am not sure). Obviously the setNeedsDisplayInRect variation would require you to have the image at hand for drawing the second time around (cache it).
If you load the image asynchronously your drawRect method will moste likely have exited before the image is loaded and the draw focus is no longer locked on your drawRect, thus making it impossible to draw the image in the context. Did you try to set a breakpoint to verify this though? I might be wrong about the locking, but thought I read something alike this at some point.

Think it through--why would it run? There's no guarantee that context is still the current context (or even that its backing store is still fully valid) by the time the block is entered. By using an asynchronous callback, be it a block or a delegate method or something else, you're intentionally deferring code to a later point in time. The call to -drawRect: has ended and is off the stack long before your block is entered.

The first thing I would try is to make sure the async handler runs on the main thread:
[service loadImage:url done:^(UIImage * image) {
dispatch_async(dispatch_get_main_queue(), ^{
CGContextDrawImage(context, frame, image.CGImage);
});
}];
If that does not help, I would start to wonder if the context is still valid. The pointer is, but the context probably not. Maybe you could move the context acquisition to the handler:
[service loadImage:url done:^(UIImage * image) {
dispatch_async(dispatch_get_main_queue(), ^{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, frame, image.CGImage);
…
});
}];
Not sure if that makes sense API-wise, I don’t do much stuff like this.

Have you tried:
__block CGContextRef context = UIGraphicsGetCurrentContext()
[service loadImage:url done:^(UIImage * image){
CGContextDrawImage(context, frame, image.CGImage);
}];
You may have to do the same thing with frame, so when the block returns it knows what context and frame it should modify.

Related

issues with CGContextRef

I am working with drawing project, as we know to draw something we need to get call this function, UIGraphicsGetCurrentcontext, So I am also calling it wherever, I need it, either its clear, or erase . What I am doing is in every function, I am calling it this way
CGContextRef context = UIGraphicsGetCurentContext()
What I want to know, is whether I get the same context whenever I call this function? whether the pointer points to same context?
Because my eraser works in some cases and fails in some cases.
Below is my eraser function
- (void)erase
{
[m_curImage drawAtPoint:CGPointMake(0, 0)];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
}
Regards
Ranjit
You can only use UIGraphicsGetCurentContext() inside of the drawRect method, outside of that it will not be configured with a valid context.
You have to create a custom view and write your erase() method code in the drawRect method.

Schedule a low-priority task on the main thread

I have a drawRect method that is rather slow (100-200ms). To save time, I need to cache the results. I am doing the actual caching like this:
// some code to check if caching would be desirable goes here. If it is desirable, then
UIGraphicsBeginImageContext(viewSize);
CGContextRef c = UIGraphicsGetCurrentContext();
[view.layer renderInContext: c];
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
[self.cachedImageArray addObject:image];
UIGraphicsEndImageContext();
The caching itself can take up to 40ms. This is still easily worth it. But the caching has to wait until everything is rendered, or it will come out wrong. Additionally, the caching is a low-priority task. Once everything is displayed, it is possible that other things will still be going on, and if so, the caching can wait. But since it uses UIKit, it has to be on the main thread.
Rather than putting in some arbitrary delay, is there a bulletproof way to wait like this?
The caching itself doesn't have to be done on the main thread. You can get a copy/reference of the image context or bitmap data, and launch it using an NSThread only when the rendering is done. Example:
- (void) drawRect:(CGRect)rect {
do_rendering_here();
// when rendering completed:
NSThread *t = [[NSThread alloc] initWithTarget:self selector:#selector(doCaching:) object:c];
[t start];
[t release];
}
- (void) doCaching:(CGContextRef)ctx {
// do whatever kind of caching is needed
}

How to access CGContext for creating CGLayer offscreen

Intention: creating CGLayer offscreen, draw some complicated stuff on it from second thread and use it later for quick drawing on main thread
Problem: CGLayerCreateWithContext(context, size, info) expects an already existing CGContext so that it knows for what kind of context it needs to be optimized.
Solution I found so far: CGContextRef ctx = UIGraphicSetCurrentContext()
but this function doesn't seem to exist anymore.
Question: Isn't there another way to access something like a default context? Or do i really need to wait for the first drawRect: call just for accessing UIGraphicsGetCurrentContext() and creating all CGLayers from main thread with one wasted drawing run?
you can create an image context by doing something like:
UIGraphicsBeginImageContext(rect);
// your drawing code
UIGraphicsEndImageContext();
with that said, i'm not sure you can do this from a thread other than main. worth a try, though.

Unable to release a CGContextRef (context is from a CGLayer)

When you create a CGLayer like so, and then get the context...it appears to be impossible to release the CGContextRef?
Releasing the CGLayerRef itself (apparently) works fine.
You'd think you could release the CGContextRef just before releasing the CGLayer - but no? Nor can you release the CGContextRef just after releasing the CGLayer.
If you release the CGContextRef, the app crashes.
CGLayerRef aether = CGLayerCreateWithContext(
UIGraphicsGetCurrentContext(), CGSizeMake(1024,768), NULL);
CGContextRef weird = CGLayerGetContext(aether);
// paths, strokes, filling etc
// paths, strokes, filling etc
// try releasing weird here
CGLayerRelease(aether);
// or, try releasing weird here
Does anyone know what is going on here? (Note further that CGContextRelease is indeed just the same as CFRelease, with some nil checking.)
In fact should you never manually release CGContextRef? Does anyone know? Cheers.
CGContextRelease(weird); // impossible, not necessary, doesn't work???
Regarding Joel's spectacular answer below:
Is releasing the CGLayerRef correct and proper? Joel has pointed out:
"Yes, since the function you're obtaining it from has 'Create' in its signature. See: documentation/CoreFoundation/"
You do not own the context returned from CGLayerGetContext, and so should not release it*. In particular, see http://developer.apple.com/library/mac/#documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/writerid/cfGetRule for information regarding 'Get' functions in Core Foundation.
*: at least, you shouldn't release it given your example code. If you retained it first (CGContextRetain(weird)), then you should have a CGContextRelease to balance it.

Quartz PDF-context based function fails (occasionally) with BAD_ACCESS

Im Getting a Frequent enough BAD_ACCESS when i call this Quartz function:
CGContextDrawPDFPage ((CGContextRef)context, (CGPDFPageRef)pageRef);
Here is how i call it:
CGContextRef context = UIGraphicsGetCurrentContext();
//translate, scale
CGPDFPageRef myPageRef = CGPDFDocumentGetPage ([PDFDocument sharedPDFDocument].documentData, pageNumber);
CGContextDrawPDFPage (context, myPageRef);//BAD_ACCESS HERE
return UIGraphicsGetImageFromCurrentImageContext();//autoreleased- saved on return
//close context
Neither variable is Nil, or have been released/auto released.
Here is the Stack trace, from the debugger:
Can anyone shed any light on this? Even some pointers on how i might investigate this better. it might not even be function specific.
I probobly should mention this function is performed on a separate thread.
Check the documentation for that returns your myPageRef and see if you have to retain it.
At the time of writing the question, UIGraphicsGetImageFromCurrentImageContext and such functions where not thread safe, this was my issue. As of iOs 4.1+ many UI function are now thread safe.