I made a function that will cycle through images (1.png, 2.png, 3.png, 4.png, 5.png) and display them to a UIImageView. The idea is that I am loading a webpage and I want this imageview to run through this function, basically as a loading icon. However I dont understand why it isn't working, as far as the imageview goes, i dont see anything. Please help! My function's code is below. I know it is something wrong i'm doing here because everything else i test including the webview delegate works just fine.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for(int c = 1; !webviewLoaded; c++)
{
c = c % 6;
if(c == 6)
c = 1;
NSString *numberString = [NSString stringWithFormat:#"%d.png", c];
UIImage *img = [UIImage imageNamed:numberString];
[self.loadingImage setImage:img];
sleep(1);
}
});
Because you are doing UI stuff on a background thread, you should't change UI components from a background thread.
You can change your code as following:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
for(int c = 1; !webviewLoaded; c++)
{
c = c % 6;
if(c == 6)
c = 1;
NSString *numberString = [NSString stringWithFormat:#"%d.png", c];
UIImage *img = [UIImage imageNamed:numberString];
[self.loadingImage setImage:img];
sleep(1);
}
}
});
This piece of code will work, but is kind of useless and it will give you results not as good as you're expecting because what you are doing is create a separate thread (operation) just to call the UI thread from it and this is not efficient.
What you should do is entirely remove the dispatch like this:
for(int c = 1; !webviewLoaded; c++)
{
c = c % 6;
if(c == 6)
c = 1;
NSString *numberString = [NSString stringWithFormat:#"%d.png", c];
UIImage *img = [UIImage imageNamed:numberString];
[self.loadingImage setImage:img];
sleep(1);
}
This will also work, but make sure you have those files #"%d.png added to your project.
And because you are not familiar with GCD here is a good tutorial
Related
i am working on this piece of code as this blocks main thread reported by time profiler.
OrderModule *module = [OrderModule sharedModule];
for(Modifier *modifier in modifierLists)
{
int merge = [module getModifierMergeOption:modifier.mModifierId productId:product.mProductId];//long process database interaction
for(ModifierItem *mod_product in modifier.activeModifiers)
{
NSString *modifierProductName = mod_product.productName;
if (merge == 1) {
modifierProductName = [modifierProductName stringByReplacingOccurrencesOfString:[NSString stringWithFormat:#"%# - ",product.mProductName] withString:#""];
}
numberOfModifiers++;
UILabel *modifierNameLabel = [[UILabel alloc] init];
// [modifierNameLabel setBackgroundColor:[UIColor redColor]];
modifierNameLabel.lineBreakMode = UILineBreakModeWordWrap;
modifierNameLabel.numberOfLines = 0;
modifierNameLabel.lineBreakMode = NSLineBreakByWordWrapping;
modifierNameLabel.numberOfLines = 0;
[modifierNameLabel setFont:DEFAULT_FONT(DEFAULT_FONTSIZE_MODIFIERNAME)];
modifierNameLabel.textColor = [UIColor colorWithRed:125.0f/255.0f green:127.0f/255.0f blue:131.0f/255.0f alpha:1.0f];
// modifierNameLabel.frame = CGRectMake(x, y, 140, 35);
modifierNameLabel.frame = CGRectMake(x, y, 120, 35);
modifierNameLabel.text = modifierProductName;
[orderDetailRow addSubview:modifierNameLabel];
UILabel *modifierAmount = [[UILabel alloc] init];
[modifierAmount setFont:DEFAULT_FONT(DEFAULT_FONTSIZE_MODIFIERNAME)];
modifierAmount.textColor = [UIColor colorWithRed:125.0f/255.0f green:127.0f/255.0f blue:131.0f/255.0f alpha:1.0f];
// modifierAmount.frame = CGRectMake(CGRectGetMaxX(modifierNameLabel.frame)+30, y, 100, 35);
modifierAmount.frame = CGRectMake(CGRectGetMaxX(modifierNameLabel.frame)+6, y, 100, 35);
[orderDetailRow addSubview:modifierAmount];
// modifiersCount++;
amountPrice=amountPrice+([mod_product.mExtraCost floatValue]*count);
[modifierAmount setText:[Utils currencyWithSymbol:[NSString stringWithFormat:#"%.02f",(([mod_product.mExtraCost floatValue]*count)+ 0.00001)]]];//simple manipulation
if ([mod_product.mExtraCost floatValue]<=0) { //3.0 changes
[modifierAmount setHidden:YES];
}
y = y + 25;
}
numberOfModifiers++;
}
[self setUpActiveModifierWithX:x andy:y Row:orderDetailRow andModifierList:modifierLists andProduct:product];
i have tried with this way:
-(void)setUpModifierWithX:(float)x andy:(float)y Row:(OrderDetailRow *)orderDetailRow andModifierList:(NSMutableArray *)modifierLists andProduct:(Product *)product
{
OrderModule *module = [OrderModule sharedModule];
float __block Y = y;
for(Modifier *modifier in modifierLists)
{
int __block merge;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
merge= [module getModifierMergeOption:modifier.mModifierId productId:product.mProductId];
dispatch_async(dispatch_get_main_queue(), ^
{
for(ModifierItem *mod_product in modifier.activeModifiers)
{
NSString *modifierProductName = mod_product.productName;
if (merge == 1) {
modifierProductName = [modifierProductName stringByReplacingOccurrencesOfString:[NSString stringWithFormat:#"%# - ",product.mProductName] withString:#""];
}
numberOfModifiers++;
UILabel *modifierNameLabel = [[UILabel alloc] init];
// [modifierNameLabel setBackgroundColor:[UIColor redColor]];
modifierNameLabel.lineBreakMode = UILineBreakModeWordWrap;
modifierNameLabel.numberOfLines = 0;
modifierNameLabel.lineBreakMode = NSLineBreakByWordWrapping;
modifierNameLabel.numberOfLines = 0;
[modifierNameLabel setFont:DEFAULT_FONT(DEFAULT_FONTSIZE_MODIFIERNAME)];
modifierNameLabel.textColor = [UIColor colorWithRed:125.0f/255.0f green:127.0f/255.0f blue:131.0f/255.0f alpha:1.0f];
// modifierNameLabel.frame = CGRectMake(x, y, 140, 35);
modifierNameLabel.frame = CGRectMake(x, y, 120, 35);
modifierNameLabel.text = modifierProductName;
[orderDetailRow addSubview:modifierNameLabel];
UILabel *modifierAmount = [[UILabel alloc] init];
[modifierAmount setFont:DEFAULT_FONT(DEFAULT_FONTSIZE_MODIFIERNAME)];
modifierAmount.textColor = [UIColor colorWithRed:125.0f/255.0f green:127.0f/255.0f blue:131.0f/255.0f alpha:1.0f];
// modifierAmount.frame = CGRectMake(CGRectGetMaxX(modifierNameLabel.frame)+30, y, 100, 35);
modifierAmount.frame = CGRectMake(CGRectGetMaxX(modifierNameLabel.frame)+6, y, 100, 35);
[orderDetailRow addSubview:modifierAmount];
// modifiersCount++;
amountPrice=amountPrice+([mod_product.mExtraCost floatValue]*count);
[modifierAmount setText:[Utils currencyWithSymbol:[NSString stringWithFormat:#"%.02f",(([mod_product.mExtraCost floatValue]*count)+ 0.00001)]]];
if ([mod_product.mExtraCost floatValue]<=0) { //3.0 changes
[modifierAmount setHidden:YES];
}
Y = Y + 25;
}
numberOfModifiers++;
});
});
}
[self setUpActiveModifierWithX:x andy:y Row:orderDetailRow andModifierList:modifierLists andProduct:product];
}
but setUpActiveModifierWithX:x is called before the for loop ends execution, how do i call setUpActiveModifierWithX:x method after that for loop.
Thanks for any suggestion
I'd step back from what you are doing, and learn how to use a TableViewController or CollectionViewController.
You seem to be creating an awful lot of subviews, which is an expensive operation. Table views and collection views avoid this by reusing the same views over and over again, just displaying different values. Instead of expensive views you use much cheaper cells. You also automatically only handle things that are actually visible, so if you have thousand items, only the dozen on your screen will take CPU time.
The easiest fix is putting it after the last statement in the block executed on the main thread:
EDIT
This below won't work, and unfortunately, there is no "easy fix" without using a third partly library. I leave the answer here for demonstrating how things quickly get more complex with asynchronous problems.
-(void)setUpModifierWithX:(float)x andy:(float)y Row:(OrderDetailRow *)orderDetailRow andModifierList:(NSMutableArray *)modifierLists andProduct:(Product *)product
{
OrderModule *module = [OrderModule sharedModule];
float __block Y = y;
for(Modifier *modifier in modifierLists)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
int merge= [module getModifierMergeOption:modifier.mModifierId productId:product.mProductId];
dispatch_async(dispatch_get_main_queue(), ^
{
for(ModifierItem *mod_product in modifier.activeModifiers)
{
...
}
numberOfModifiers++;
[self setUpActiveModifierWithX:x andy:y Row:orderDetailRow andModifierList:modifierLists andProduct:product];
});
});
}
}
It turned out that this seemingly easy task isn't actually that easy. I couldn't find a solution that's easier than the following, without resorting to a third party library which makes such tasks much easier (I'll propose a solution using an utility library later).
So, here is an approach which uses a recursive block.
WARNING
Recursive blocks, are tricky - and I wouldn't recommend to use them unless you know why and how you apply this technique.
The code snippet shows the principal approach, but your original problem needs to be adjusted.
Your method
-(void)setUpModifierWithX:(float)x andy:(float)y Row:(OrderDetailRow *)orderDetailRow andModifierList:(NSMutableArray *)modifierLists andProduct:(Product *)product
becomes now an asynchronous method:
-(void) foo
{
NSArray* array = #[#"a", #"b", #"c"]; // your input array, e.g. "modifierLists"
NSMutableArray* marray = [array mutableCopy]; // we need a mutable array here
typedef void (^completion_t)(id result);
typedef void (^block_t)(NSMutableArray*, completion_t);
// setting up block variable suitable for using that block recursively:
__block __weak block_t weak_block;
block_t block;
weak_block = block = ^(NSMutableArray*array, completion_t completion) {
// Check the termination condition for the "loop"
if ([array count] == 0) {
if (completion) {
completion(#"Finished");
}
return;
}
id item = array[0];
[array removeObjectAtIndex:0];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// process "item" on a secondary thread (private queue):
int merge = [item characterAtIndex:0]; //[module getModifierMergeOption:modifier.mModifierId productId:product.mProductId];
dispatch_async(dispatch_get_main_queue(), ^{
// On the main thread, setup the UIKit objects:
NSLog(#"%d", merge);
/*
for(ModifierItem *mod_product in modifier.activeModifiers)
{
...
}
numberOfModifiers++;
*/
// Continue the loop:
weak_block(array, completion); //looks scary!
});
});
};
// Start the loop:
block(marray, ^(id result){
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"result: %#", result);
//[self setUpActiveModifierWithX:x andy:y Row:orderDetailRow andModifierList:modifierLists andProduct:product];
});
});
}
Regarding recursive blocks, see this question ARC behavior within a recursive block and this answer on SO: https://stackoverflow.com/a/19626441/465677
we're loading about 30 full 320 x 480 images into an animations array. These images are very small in size at about 5 - 7Kb per images. The problem is when we run the loop to populate an array for the images, it seems to pause the entire app while this array get's populated. Is there perhaps a way to load this mutable array with the images in a separate thread or something to allow the array to populate without hurting performance? Thanks
NSMutableArray *totalAnimationImages = [[NSMutableArray alloc] initWithCapacity:numOfImages];
for(int i = 1; i < numOfImages; i++) {
[totalAnimationImages addObject:[UIImage imageNamed:
[NSString stringWithFormat:#"%#%d.png", image, i]]];
}
annImage.animationImages = totalAnimationImages;
annImage.animationDuration = speed; //.4 second
annImage.animationRepeatCount = 1; //infinite loop
[annImage startAnimating]; //start the animation
[totalAnimationImages release];
In working with this issue, and using the threading suggestion provided by shabzco, I've come up with the below code to process a loop, changing the image in an image view, with what seem to be absolutely no performance hit. This only holds one image at a time instead of 30 in an NSMutableArray. Just as Richard J. Ross III said, as soon as the animation would start it would be processing all of the images "into a in-memory bitmap format". This is where the long wait / lag came from. In the below code this is only happening once per image update instead of 30 just to start the animation. I use the while loop with timer in the suggested dispatch thread to keep this timer from effecting the main thread thus potentially creating jerky performance during animations. Please provide feedback on this solution! Thanks
UPDATE: Shabzco suggested that I replace the "ImageNamed" with "imageWithContentsOfFile" to avoid strange memory leaks due to ImageNamed potentially caching images in memory. This was discovered after running xcode profiler and watching the "Real Memory" counter. It became evident that every time an animation ran, the real memory would increase more and more until ultimately the app would crash. After replacing "ImageNamed" with "imageWithContentsOfFile" the issue went away. I've updated the below code.
annImage.image = [UIImage imageNamed:#""];
[annImage setAlpha:1.0];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
NSTimeInterval timeout = 1.00 / speed; // Number of seconds before giving up
NSTimeInterval idle = 1.00 / 8; // Number of seconds to pause within loop
BOOL timedOut = NO;
NSDate *timeoutDate;
for(int i = 1; i < numOfImages; i++) {
timeoutDate = [[NSDate alloc] initWithTimeIntervalSinceNow:timeout];
timedOut = NO;
while (!timedOut)
{
NSDate *tick = [[NSDate alloc] initWithTimeIntervalSinceNow:idle];
[[NSRunLoop currentRunLoop] runUntilDate:tick];
timedOut = ([tick compare:timeoutDate] == NSOrderedDescending);
[tick release];
}
dispatch_async(dispatch_get_main_queue(), ^{
//annImage.image = [UIImage imageNamed:
// [NSString stringWithFormat:#"%#%d.png", image, i]];
NSString *filePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"%#%d", image, i] ofType:#"png"];
annImage.image = [UIImage imageWithContentsOfFile:filePath];
});
}
dispatch_async(dispatch_get_main_queue(), ^{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration: 0.3];
[annImage setAlpha:0.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[UIView commitAnimations];
});
});
This will make your for loop happen in a different thread and will start animating once everything has been loaded.
#autoreleasepool {
NSMutableArray *totalAnimationImages = [[NSMutableArray alloc] initWithCapacity:numOfImages];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
for(int i = 1; i < numOfImages; i++) {
[totalAnimationImages addObject:[UIImage imageNamed:
[NSString stringWithFormat:#"%#%d.png", image, i]]];
}
annImage.animationImages = totalAnimationImages;
annImage.animationDuration = speed; //.4 second
annImage.animationRepeatCount = 1; //infinite loop
[annImage startAnimating]; //start the animation
dispatch_async(dispatch_get_main_queue(), ^{
[totalAnimationImages release];
});
});
}
I just want to prerender different images for fast access.
I use grand central dispatch here to execute the different blocks.
After starting the queue, I want to set the first image when its done.
With the current code below, unfortunately the first image will only be displayed when all images has been rendered.
So how can i modify the code? Is it possible to get a delegate when each image has finished?
Here´s the code:
// Async prerendering
for (int i = 0; i < count; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIImage* finalImage = [self prerenderImageForIndex:i];
[self.imageArray addObject:finalImage];
// TODO: i want to display the first image.
// rendering goes on in the background
if (i==0 && [self.imageArray objectAtIndex:0] != nil ) {
self.image = [self.imageArray objectAtIndex:0];
}
});
});
}
Update:
-(UIImage*) prerenderImageForIndex:(int)frame {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.frame.size.width, self.frame.size.height), NO, 0);
for (int i=0; i< [configurationArray count]; i++) {
//... get the layerName
UIImage* layerImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:layerName ofType:#"png"]];
// draw layer with blendmode and alpha
[layerImage drawInRect:CGRectMake(x, y, layerImage.size.width, layerImage.size.height)
blendMode:layerblendmode
alpha: layeralpha];
}
// Get current context as an UIImage
UIImage* finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return finalImage;
}
i just want to know how do i cancel/stop or restart a running queue?
Is that possible?
Thanks for your help.
you have to use a serial queue, which executes FIFO for example:
dispatch_queue_t queue;
queue = dispatch_queue_create("myImageQueue", NULL);
for(int i = 0; i<count; i++) {
dispatch_async(queue, ^{
// do your stuff in the right order
});
}
for serial Dispatch queues look at:
http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
I'm not sure why you have the dispatch_async calls nested like this but maybe that's the problem. I would imagine something like below would accomplish what you want. You only need to get the main queue when you actually want to do the UI update, everything else should be done on the background queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
UIImage* finalImage = [self prerenderImageForIndex:i];
[self.imageArray addObject:finalImage];
if (i==0 && [self.imageArray objectAtIndex:0] != nil ) {
dispatch_async(dispatch_get_main_queue(), ^{
self.image = [self.imageArray objectAtIndex:0];
});
}
});
I am trying to create an app where when you tap a button it will go to the next frame in an animation.
I have 8 image files, and when I press the button I want the 1st image to display, and when i press the button again, I want the 2nd image to replace the 1st image and so on.
I was thinking something like:
-(IBAction)buttonPressDoStuff:(id)sender {
imageThing.image = [UIImage imageNamed:#"image1.png"];
imageThing.image = [UIImage imageNamed:#"image2.png"];
imageThing.image = [UIImage imageNamed:#"image3.png"];
imageThing.image = [UIImage imageNamed:#"image4.png"];
}
and somehow making this all work consecutively with each press.
I am fairly new at objective c, so any help would be much appreciated.
Can anyone throw up some sample code to do this?
Let's think about this. If you want to do something sequentially, that sounds like the job of an array. So what do you think about this:
In your .h file, add these instance variables:
NSMutableArray* picturesArray;
NSInteger counter;
And now in your .m file, in your class' init method:
//this loop will fill your array with the pictures
for(int idx = 0; idx < NUMBER_OF_PICTURES; idx++) {
//IMPORTANT: this assumes that your pictures' names start with
//'image0.png` for the first image, then 'image1.png`, and so on
//if your images' names start with 'image1.png' and then go up, then you
//should change the 'int idx = 0' declaration in the for loop to 'int idx = 1'
//so the loop will start at 0. You will then need to change the condition
//to 'idx < (NUMBER_OF_PICTURES + 1)' to accomodate the last image
NSString* temp = [NSString stringWithFormat:#"image%i.png", idx];
UIImage* tempImage = [UIImage imageNamed:temp];
[picturesArray addObject:tempImage];
}
and in your buttonPressDoStuff: method:
//this method will move to the next picture in the array each time it is pressed
-(IBAction)buttonPressDoStuff:(id)sender {
if(counter < [pictureArray count]) {
imageThing.image = [picturesArray objectAtIndex:counter];
counter++;
}
}
Your init method should look something like this:
- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self) {
//do setup here
for(int idx = 0; idx < NUMBER_OF_PICTURES; idx++) {
NSString* temp = [NSString stringWithFormat:#"image%i.png", idx];
UIImage* tempImage = [UIImage imageNamed:temp];
[picturesArray addObject:tempImage];
}
}
//it is important that you return 'self' no matter what- if you don't,
//you will get the 'control reached end of non-void method' warning
return self;
}
I am trying to modify Apple's PhotoScroller example and I encountered a problem that I couldn't solve.
Basically, the PhotoScroller originally loaded a bunch of image in an array locally. I try to modify this and change to it request an image file dynamically from an URL. Once the user scroll to the next page, it will fetch the next image from a new URL.
In order to improve the performance, I wanted to preload the next page so user doesn't need to wait for the image being downloaded while scrolling to the next page. Once the next page is on current page, the page after that will be loaded and so on...
I'm not quite sure how I can achieve this and I hope someone can show me what to do.
Here is my custom code: (Please refer the full code from Apple's PhotoScroller example)
tilePage method: (It will be call at the beginning and every time when user did scroll the scrollView)
- (void)tilePages
{
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, [self imageCount] - 1);
// Recycle no-longer-visible pages
for (ImageScrollView *page in visiblePages) {
if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
[recycledPages addObject:page];
[page removeFromSuperview];
}
}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
if (![self isDisplayingPageForIndex:index]) {
ImageScrollView *page = [self dequeueRecycledPage];
//ImageScrollView *nextpage = [self dequeueRecycledPage];
if (page == nil) {
page = [[[ImageScrollView alloc] init] autorelease];
}
[self configurePage:page forIndex:index];
[pagingScrollView addSubview:page];
[visiblePages addObject:page];
}
}
}
To configure page index and content:
- (void)configurePage:(ImageScrollView *)page forIndex:(NSUInteger)index
{
//set page index
page.index = index;
//set page frame
page.frame = [self frameForPageAtIndex:index];
//Actual method to call image to display
[page displayImage:[self imageAtIndex:index]];
NSLog(#"index: %i", index);
}
To fetch image from URL:
- (UIImage *)imageAtIndex:(NSUInteger)index {
NSString *string1 = [NSString stringWithString:#"http://abc.com/00"];
NSString *string2 = [NSString stringWithFormat:#"%d",index+1];
NSString *string3 = [NSString stringWithString:#".jpg"];
NSString *finalString = [NSString stringWithFormat:#"%#%#%#",string1,string2,string3];
NSLog(#"final string is: %#", finalString);
NSURL *imgURL = [NSURL URLWithString: finalString];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
return [UIImage imageWithData:imgData];
}
Thanks for helping!
Lawrence
You can use concurrent operations to fetch images one after another. Using NSOperations, you can set a dependency chain so images are loaded in a serial fashion in the background, or you can have them all downloaded concurrently.
The problem with large images is that even though you save them in the file system, there is a "startup" time to get the images rendered. Also, in PhotoScroller, Apple "cheats" by having all the images pre tiled for each level of detail. Apple provides no way to render just a Plain ole JPEG in PhotoScroller, so unless you can pre tile it will be of no use to you.
If you are curious, you can see how to both download multiple images and pre tile them into a temporary file by perusing the PhotoScrollerNetwork project on github.