My program displays a horizontal scrolling surface tiled with UIImageViews from left to right. Code runs on the UI thread to ensure that newly-visible UIImageViews have a freshly loaded UIImage assigned to them. The loading happens on a background thread.
Everything works almost fine, except there is a stutter as each image becomes visible. At first I thought my background worker was locking something in the UI thread. I spent a lot of time looking at it and eventually realized that the UIImage is doing some extra lazy processing on the UI thread when it first becomes visible. This puzzles me, since my worker thread has explicit code for decompressing JPEG data.
Anyway, on a hunch I wrote some code to render into a temporary graphics context on the background thread and - sure enough, the stutter went away. The UIImage is now being pre-loaded on my worker thread. So far so good.
The issue is that my new "force lazy load of image" method is unreliable. It causes intermittent EXC_BAD_ACCESS. I have no idea what UIImage is actually doing behind the scenes. Perhaps it is decompressing the JPEG data. Anyway, the method is:
+ (void)forceLazyLoadOfImage: (UIImage*)image
{
CGImageRef imgRef = image.CGImage;
CGFloat currentWidth = CGImageGetWidth(imgRef);
CGFloat currentHeight = CGImageGetHeight(imgRef);
CGRect bounds = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
CGAffineTransform transform = CGAffineTransformIdentity;
CGFloat scaleRatioX = bounds.size.width / currentWidth;
CGFloat scaleRatioY = bounds.size.height / currentHeight;
UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleRatioX, -scaleRatioY);
CGContextTranslateCTM(context, 0, -currentHeight);
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(0, 0, currentWidth, currentHeight), imgRef);
UIGraphicsEndImageContext();
}
And the EXC_BAD_ACCESS happens on the CGContextDrawImage line. QUESTION 1: Am I allowed to do this on a thread other than the UI thread? QUESTION 2: What is the UIImage actually "pre-loading"? QUESTION 3: What is the official way to solve this problem?
Thanks for reading all that, any advice would be greatly appreciated!
I've had the same stuttering problem, with some help I figured out the proper solution here: Non-lazy image loading in iOS
Two important things to mention:
-
I've posted a sample project here: SwapTest, it has about the same performace as Apples' Photos app for loading/displaying images.
I had the same problem, even though I initialized the image using data. (I guess the data is loaded lazily, too?) I’ve succeeded to force decoding using the following category:
The
UIGraphics*
methods are designed to be called from the main thread only. They are probably the source of your trouble.You can replace
UIGraphicsBeginImageContext()
with a call toCGBitmapContextCreate()
; it's a little more involved (you need to create a color space, figure out the right sized buffer to create, and allocate it yourself). TheCG*
methods are fine to run from a different thread.I'm not sure how you're initializing UIImage, but if you're doing it with
imageNamed:
orinitWithFile:
then you might be able to force it to load by loading the data yourself and then callinginitWithData:
. The stutter is probably due to lazy file I/O, so initializing it with a data object won't give it the option of reading from a file.I used @jasamer's SwapTest UIImage category to force load my large UIImage (about 3000x2100 px) in a worker thread (with NSOperationQueue). This reduces the stutter time when setting the image into the UIImageView to an acceptable value (about 0.5 sec on iPad1).
Here is SwapTest UIImage category... thanks again @jasamer :)
UIImage+ImmediateLoading.h file
UIImage+ImmediateLoading.m file
And this is how I create NSOpeationQueue and set the image on main thread...