UIImageView and UIScrollView load lot of pictures - iphone

In my app, I use UIImageView and UIScrollView to show a lot of images (every time there are about 20 images and every image is about 600px*500px and size is about 600kb).
I use this code to accomplish this function:
// Here is pictures Data;
self.klpArry = self.pictureData;
CGSize size = self.klpScrollView1.frame.size;
for (int i=0; i < [klpArr count]; i++) {
UIImageView *iv = [[UIImageView alloc] initWithFrame:CGRectMake((size.width * i)+300, 20, 546, 546)];
NSString *filePath = [[NSBundle mainBundle] pathForResource:[klpArr objectAtIndex:i] ofType:#"jpg"];
UIImage *imageData = [[UIImage alloc]initWithData:[NSData dataWithContentsOfFile:filePath]];
[iv setImage:imageData];
iv.backgroundColor = [UIColor grayColor];
[self.klpScrollView1 addSubview:iv];
imageData = nil;
iv = nil;
iv.image = nil;
filePath = nil;
[imageData release];
[filePath release];
[iv release];
}
// show the picture in scrollview;
[self.klpScrollView1 setContentSize:CGSizeMake(size.width * numImage, size.height)];
self.klpScrollView1.pagingEnabled = YES;
self.klpScrollView1.showsHorizontalScrollIndicator = NO;
self.klpScrollView1.backgroundColor = [UIColor grayColor];
But every time I initialize this function, the memory will increase about 5MB. Actually I release UIImageView, UIimage and UIScrollView (vi.image=nil, [vi release]) but it doesn't work, the allocated memory is not getting released. BTW, I used my friend's code first vi.image = nil then vi = nil; but the pictures are not getting displayed on scrollview.

The main problem, as I see it, is that you're setting your local variables to nil and then you proceed to try to use those local variables in methods like release and the like, but because they've been set to nil, those methods now do nothing, and the objects with the +1 retainCount (or now +2 because you've added them to your view) are never released.
Thus, I'd suggest the following:
//Here is pictures Data;
self.klpArry = self.pictureData;
CGSize size = self.klpScrollView1.frame.size;
for (int i=0; i < [klpArr count]; i++) {
UIImageView *iv = [[UIImageView alloc] initWithFrame:CGRectMake((size.width * i)+300, 20, 546, 546)];
NSString *filePath = [[NSBundle mainBundle] pathForResource:[klpArr objectAtIndex:i] ofType:#"jpg"];
UIImage *imageData = [[UIImage alloc]initWithData:[NSData dataWithContentsOfFile:filePath]];
[iv setImage:imageData];
iv.backgroundColor = [UIColor grayColor];
[self.klpScrollView1 addSubview:iv];
// Don't set these to nil, or else subsequent release statements do nothing!
// These statements are actually not necessary because they refer to local
// variables so you don't need to worry about dangling pointers. Make sure
// you're not confusing the purpose of setting a pointer to nil in ARC to
// what you're doing in your non-ARC code.
//
// imageData = nil;
// iv = nil;
// You don't want to set this to nil because if iv is not nil, your image
// will be removed from your imageview. So, not only is this not needed,
// but it's undesired.
//
// iv.image = nil;
// This statement is not necessary for the same reason you don't do it
// to imageData or iv, either. This is probably even worse, though, because
// filePath is not a variable that you initialized via alloc. You should
// only be releasing things you created with alloc (or new, copy, mutableCopy,
// for which you issued a retain statement).
//
// filePath = nil;
[imageData release];
// filePath is a +0 retainCount already, so don't release. You only release
// those items for which you increased retainCount (e.g. via new, copy,
// mutableCopy, or alloc or anything you manually retained).
//
// [filePath release];
[iv release];
}
// show the picture in scrollview;
[self.klpScrollView1 setContentSize:CGSizeMake(size.width * numImage, size.height)];
self.klpScrollView1.pagingEnabled = YES;
self.klpScrollView1.showsHorizontalScrollIndicator = NO;
self.klpScrollView1.backgroundColor = [UIColor grayColor];
Thus, your code would be simplified (and corrected) to probably just be:
//Here is pictures Data;
self.klpArry = self.pictureData;
CGSize size = self.klpScrollView1.frame.size;
for (int i=0; i < [klpArr count]; i++) {
UIImageView *iv = [[UIImageView alloc] initWithFrame:CGRectMake((size.width * i)+300, 20, 546, 546)];
NSString *filePath = [[NSBundle mainBundle] pathForResource:[klpArr objectAtIndex:i] ofType:#"jpg"];
UIImage *imageData = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:filePath]];
[iv setImage:imageData];
iv.backgroundColor = [UIColor grayColor];
[self.klpScrollView1 addSubview:iv];
[imageData release];
[iv release];
}
// show the picture in scrollview;
[self.klpScrollView1 setContentSize:CGSizeMake(size.width * numImage, size.height)];
self.klpScrollView1.pagingEnabled = YES;
self.klpScrollView1.showsHorizontalScrollIndicator = NO;
self.klpScrollView1.backgroundColor = [UIColor grayColor];
Or you could further simplify your code through the use of autorelease:
//Here is pictures Data;
self.klpArry = self.pictureData;
CGSize size = self.klpScrollView1.frame.size;
for (int i=0; i < [klpArr count]; i++) {
UIImageView *iv = [[[UIImageView alloc] initWithFrame:CGRectMake((size.width * i)+300, 20, 546, 546)] autorelease];
NSString *filePath = [[NSBundle mainBundle] pathForResource:[klpArr objectAtIndex:i] ofType:#"jpg"];
UIImage *imageData = [[[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:filePath]] autorelease];
[iv setImage:imageData];
iv.backgroundColor = [UIColor grayColor];
[self.klpScrollView1 addSubview:iv];
}
// show the picture in scrollview;
[self.klpScrollView1 setContentSize:CGSizeMake(size.width * numImage, size.height)];
self.klpScrollView1.pagingEnabled = YES;
self.klpScrollView1.showsHorizontalScrollIndicator = NO;
self.klpScrollView1.backgroundColor = [UIColor grayColor];
By the way, the statement (with autorelease):
UIImage *imageData = [[[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:filePath]] autorelease];
could probably be simplified to:
UIImage *imageData = [UIImage imageWithContentsOfFile:filePath];
This gives you a UIImage, with a +0 retainCount (i.e. you don't have to release it) from your file.
So, a few final observations:
You really should probably review and study the Advanced Memory Management Programming Guide. It's dense reading if you're new to memory management, but mastery of these concepts (especially in non-ARC code) is critical.
If you haven't, I'd encourage you to run your code through the static analyzer ("Product" - "Analyze" or shift+command+B). I'd be surprised if many (if not most) of these issues wouldn't have been identified for you by the analyzer. You should have zero warnings when you run your code through the analyzer.
If you want to take this to the next level, you might want to be far more conservative about your memory management. The governing principle would be a system design that only loads the images that are needed at the UI at any given time, which involves not only calvinBhai's excellent suggestion of lazy loading of images (i.e. don't load images into memory until your UI really needs them), but also a pro-active releasing images once they've scrolled off the screen. Maybe you don't need to worry about it in your app, because you're only dealing with 20 images at a time, but if any of your portfolios/galleries expanded to 100 or 1000 images, the idea of keeping all of those in memory at any given time is just a bad idea. This is a more advanced concept, so maybe you should focus on the basic memory management problems of your existing code first, but longer term, you might want to contemplate lazy loading and pro-active releasing of images.

If memory is your concern,
try lazy loading the images = Load the visible image, the next and previous image. You dont have to have all images added to your klpscrollview.
Once you figure out lazy loading the images onto your scrollview, then you can think of fixing the images not showing on your scrollview.
Easier would be to search for "lazy load uiimage uiscrollview"

Related

How to correctly preload image arrays for animation

I have several animations which, when triggered, have varying lengths of (unintentional) delays before they execute.
Inside viewDidLoad i have something like:
NSString *fileName;
myArray = [[NSMutableArray alloc] init];
for(int i = 1; i < 285; i++) {
fileName = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"HD1.2 png sequence/HD1.2_%d", i] ofType:#"png"];
UIImage *image = [UIImage imageWithContentsOfFIle:fileName];
[humptyArray addObject:image];
//NSLog(#"Added object number %d: %#", i,regularImage);
}
falling.userInteractionEnabled = NO;
falling.animationImages = humptyArray;
falling.animationDuration = 6.3f;
falling.animationRepeatCount = 1;
I put in the NSLog so i could confirm that the array is populated with the images, which it is. When i want to trigger the animation i call [falling startAniamting]. Although the array has been preloaded with images there is still a delay between me triggering the animation and the animation executing.
What can i do so that there is no delay when i trigger the animation?
#autoreleasepool {
NSString *fileName;
humptyArray = [[NSMutableArray alloc] init];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
for(int i = 1; i < 285; i++) {
fileName = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"HD1.2 png sequence/HD1.2_%d", i] ofType:#"png"];
UIImage *image = [UIImage imageWithContentsOfFIle:fileName];
[humptyArray addObject:image];
//NSLog(#"Added object number %d: %#", i,regularImage);
}
falling.userInteractionEnabled = NO;
falling.animationImages = humptyArray;
falling.animationDuration = 6.3f;
falling.animationRepeatCount = 1;
dispatch_async(dispatch_get_main_queue(), ^{
[humptyArray release];
});
});
}
Taken from my own answer in this other [SO question][1]
[1]: http://stackoverflow.com/a/11364856/253008
You will find that people on SO suggest using animationImages, but it can eat up all your system memory and it is not fast. Please have a look at my answer to smooth-video-looping-in-ios, it is talking about looping video but the issue is the same. Please also take a look at my answer to iphone-smooth-transition-from-one-video-to-another. There are 2 example xcode projects available at these links that show an implementation that will start playing quickly and loop without any glitch all without eating up all your memory and causing an app crash.

I want to display a image in UIImagaView tat image names stored in array .so how to get string from array in iphone

Actually i want to display iamge in UIImageView ...tat image names are getting from my database..so how to display image from getting string in an array in iphone
use this methods
UIImageView *imageView = [UIImageView alloc] initWithFrame:CGRectMake(0,0,320,480)];
imageView.animationImages = animationImages ;
imageView.animationRepeatCount = 2;
imageView.animationDuration= 4.0;
[imageView startAnimating];
animationImages is an Array containig UIImage Objects.
NSString *imageNameStr = [NSString stringWithFormat:#"%#",[imageNameArray objectAtIndex:index]];
UIImageView *myImage;
myImage=[[UIImageView alloc]initWithFrame:CGRectMake(00.0, 00.0, 320.0, 480.0)];
myImage.backgroundColor=[UIColor clearColor];
myImage.image=[UIImage imageNamed:imageNameStr];
[self.view addSubview:myImage];
hope it Help To you
You can use the following code.
NSString *str = [NSString stringWithFormat:#"%#",[arr_ArrayName objectAtIndex:index]];
NSString *path = [[ NSBundle mainBundle] pathForResource:str ofType:#"png"];
UIImage *temp = [UIImage imageWithContentsOfFile:path];
yourImageView.image = temp;
Pass the index value as an integer which image from array you want to display.
Edit:
Declare one integer temp in your view will appear. Set it's value to 0. temp = 0;
Then, inside your button action please write the below mentioned code:
-(void)buttonClick
{
NSString *selectedItem =[NSString stringWithFormat:#"%#",[colors objectAtIndex:temp]];
NSString *wrdstr = [[ NSBundle mainBundle] pathForResource:selectedItem ofType:#"png"];
UIImage *temp = [UIImage imageWithContentsOfFile:wrdstr];
imgshow.image = temp;
if(temp <= [colors count])
temp = temp + 1;
}

how to release memory from animating UIImage Array

I will like to know how to release the memory from the animating array, somehow in instruments the animating picture are eating up 24MB of real memory. what should i do to release the memory ? the total picture file size are about 3MB in Mac OS.
*EDIT:*Cool, i enabled the ARC setting in the build setting, no longer crashing...but memory usages still hovering around 80 - 120mb physical memory.....
this is the line that run for the image.
-(void)defaultSetup
{
self.imageArray = [[NSMutableArray alloc] initWithObjects:
[UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"default/d7.jpg"]],
[UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"default/d9.jpg"]],
[UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"default/d11.jpg"]],
[UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"default/d27.jpg"]],
[UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"default/d6.jpg"]],
nil];
self.defaultID = [[NSMutableArray alloc] initWithObjects:#"0",#"1",#"2",#"3",#"0",nil];
self.defaultImageCaption = [[NSMutableArray alloc] initWithObjects:#"Ver",#"Green",#"Red",#"Cru",#"East",nil];
NSUInteger count = [self.defaultID count];
NSLog(#"Count %i",[self.defaultID count]);
NSMutableArray *randomImgName = self.imageArray;
NSMutableArray *randomID = self.defaultID;
NSMutableArray *randomName = self.defaultImageCaption;
for (NSUInteger i = 0; i < count; ++i) {
// Select a random element between i and end of array to swap with.
int nElements = count - i;
int n = (arc4random() % nElements) + i;
[randomName exchangeObjectAtIndex:i withObjectAtIndex:n];
[randomImgName exchangeObjectAtIndex:i withObjectAtIndex:n];
[randomID exchangeObjectAtIndex:i withObjectAtIndex:n];
}
self.imageArray = randomImgName;
self.defaultID=randomID;
self.defaultImageCaption=randomName;
NSLog(#"default filename %#",self.defaultImageCaption);
NSLog(#"default ID %#",self.defaultID);
self.imageViewTop.alpha = 1.0;
self.imageViewBottom.alpha = 0.0;
self.imageViewBottom = [[[UIImageView alloc] initWithFrame:CGRectMake(0,44,320,367)] autorelease];
self.imageViewTop = [[[UIImageView alloc] initWithFrame:CGRectMake(0,44,320,367)] autorelease];
[self.view addSubview:imageViewTop];
[self.view addSubview:imageViewBottom];
self.buttonCaption = [UIButton buttonWithType:UIButtonTypeCustom];
//default placement
self.buttonCaption.frame = CGRectMake(245, 365, 70, 30);
//[buttonCaption setTitle:#"\u00A9" forState:UIControlStateNormal];
[self.buttonCaption addTarget:self action:#selector(buttonCheck) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.buttonCaption];
[self nextAnimation:buttonCaption.frame.size.width];
}
-(void)nextAnimation:(float)previousWidth {
//picture loop
imageViewTop.image = imageViewBottom.image;
imageViewBottom.image = [imageArray objectAtIndex:[imageArray count] - 1];
[imageArray insertObject:imageViewBottom.image atIndex:0];
[imageArray removeLastObject];
imageViewTop.alpha = 1.0;
imageViewBottom.alpha = 0.0;
//Name Caption
NSString * tempCaption = [defaultImageCaption objectAtIndex:[defaultImageCaption count]-1];
self.dID = [defaultID objectAtIndex:[defaultID count]-1];
// make the buttons content appear in the top-left
[buttonCaption setContentHorizontalAlignment:UIControlContentHorizontalAlignmentCenter];
[buttonCaption setContentVerticalAlignment: UIControlContentVerticalAlignmentCenter ];
[defaultImageCaption insertObject:tempCaption atIndex:0];
[defaultImageCaption removeLastObject];
[defaultID insertObject:dID atIndex:0];
[defaultID removeLastObject];
[UIView animateWithDuration:1 delay:2 options:0
animations:^{
[buttonCaption setTitle:tempCaption forState:UIControlStateNormal];
//NSLog(#"Name %#",tempCaption );
//NSLog(#"ID %#",dID);
}
completion:^(BOOL completed)
{
}];
Read up on Cocoa memory management. (I'm assuming you're not using ARC (automatic reference counting).) Btw, I'd consider using ARC, it makes your life easier. (It's a compile-time technology, so is backwards compatible with older iOS runtimes, but you don't get weak reference support on older iOS runtimes.)
To summarise the memory management rules: everything that increases your retain count on an object should be balanced by something that decreases the retain count at some point.
Things that increment the retain count:
self.propertyName = anInstance, when that property is declared with retain property.
[objectInstance retain].
[Class alloc], [Class new], [objectInstance copy] or variants beginning with copy.
Adding an object to a standard collection class (e.g. NSArray).
Things that decrement the retain count:
self.propertyName = <some other object instance or nil>, when that property is declared with retain property.
[objectInstance release]
[objectInstance autorelease] (causes a release at some later time).
Removing an object from a standard collection class (e.g. NSArray).
When something is created with alloc and init or an initWith* (as is done here with imageArray) you can release it with release:
[self.imageArray release];
If there's autorelease or retain calls involved, things can get a little bit more tricky. But they don't seem to come into play here.
If you load the images lazily (rather than getting array of UIImages imageArray make array of their locations) you can save lot of memory, and can easily clean up UIImage instances.
By doing this, the max images loaded in memory would not be more than 3; it can save RAM.
example as requested:
while initializing the array,
self.imageArray = [[NSMutableArray alloc] initWithObjects:
#"default/d7.jpg",
#"default/d9.jpg",
#"default/d11.jpg",
#"default/d27.jpg",
#"default/d6.jpg",
nil];
while loading actual image
NSString* imgPath = [imageArray objectAtIndex:[imageArray count] - 1];
imgPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:imgPath];
imageViewBottom.image = [UIImage imageWithContentsOfFile:imgPath];
Let me know if it helps!

UIImageView animation delay when doing startAnimating

I have an animation of a menu background that is 21 frames. I load them into memory using the below code in the view's viewDidLoad method.
NSMutableArray *menuanimationImages = [[NSMutableArray alloc] init];
for( int aniCount = 0; aniCount < 21; aniCount++ )
{
NSString *fileLocation = [[NSBundle mainBundle] pathForResource: [NSString stringWithFormat: #"bg%i", aniCount + 1] ofType: #"png"];
NSData *imageData = [NSData dataWithContentsOfFile: fileLocation];
[menuanimationImages addObject: [UIImage imageWithData:imageData]];
}
settingsBackground.animationImages = menuanimationImages;
Unfortunately, doing [settingsBackground startAnimating]; doesn't work in the viewDidLoad method. Is there some way to preload the animation so there isn't a 1-3 second delay on first run?
I wouldn't normally recommend using imageNamed and relying on the in-built caching mechanisms. You'll find a lot of discussion on this if you search, but also it won't necessarily pre-render your images anyway.
I use the following code to pre-load and pre-render images so there is no delay when animating the first time through.
NSMutableArray *menuanimationImages = [[NSMutableArray alloc] init];
for (int aniCount = 1; aniCount < 21; aniCount++) {
NSString *fileLocation = [[NSBundle mainBundle] pathForResource: [NSString stringWithFormat: #"bg%i", aniCount + 1] ofType: #"png"];
// here is the code to pre-render the image
UIImage *frameImage = [UIImage imageWithContentsOfFile: fileLocation];
UIGraphicsBeginImageContext(frameImage.size);
CGRect rect = CGRectMake(0, 0, frameImage.size.width, frameImage.size.height);
[frameImage drawInRect:rect];
UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[menuanimationImages addObject:renderedImage];
}
settingsBackground.animationImages = menuanimationImages;
The problem with each of these answers is that the suggested "fix" is to preload all your image data into memory. That can and will result in crashes if your images are too big or there are too many images, see my answer for uiimage-animation-causing-app-to-crash-memory-leaks for more detailed info. The real fix is that you need to decompress the images but not hold them all in memory. Or, you can use a movie format that already does the decompression for all frames in one step, so that changing from frame to frame is not a costly operation.
The animationImages property of UIImageView requires an array of UIImage objects, not an array of NSData objects.
You may also want to set the duration and repeatCount, even though they have default values.
Example:
if (!self.idleView.animationImages) {
self.idleView.animationImages = [NSArray arrayWithObjects:[UIImage imageNamed:#"idle1.png"],
[UIImage imageNamed:#"idle2.png"],
[UIImage imageNamed:#"idle3.png"],
[UIImage imageNamed:#"idle4.png"],
[UIImage imageNamed:#"idle5.png"],
[UIImage imageNamed:#"idle6.png"],
[UIImage imageNamed:#"idle7.png"],
[UIImage imageNamed:#"idle8.png"],
[UIImage imageNamed:#"idle9.png"],
[UIImage imageNamed:#"idle10.png"],
[UIImage imageNamed:#"idle11.png"],
[UIImage imageNamed:#"idle12.png"],
[UIImage imageNamed:#"idle13.png"],
[UIImage imageNamed:#"idle14.png"],
[UIImage imageNamed:#"idle15.png"],
[UIImage imageNamed:#"idle16.png"],
[UIImage imageNamed:#"idle17.png"],
[UIImage imageNamed:#"idle18.png"],
[UIImage imageNamed:#"idle19.png"],
[UIImage imageNamed:#"idle20.png"]
, nil];
}
self.idleView.animationRepeatCount = 0;
self.idleView.animationDuration = 2.0;
[self.view addSubview:self.idleView];
[self.view sendSubviewToBack:self.idleView];
[self.idleView startAnimating];
Try the following code, normally you should have no delays. Also this code is simpler :
NSMutableArray *menuanimationImages = [[NSMutableArray alloc] initWithCapacity:21];
NSString *imageName;
for( int aniCount = 1; aniCount < 21; aniCount++ )
{
imageName = [NSString stringWithFormat:#"bg%d.png", aniCount];
[menuanimationImages addObject:[UIImage imageNamed:imageName]];
}
settingsBackground.animationImages = menuanimationImages;
// all frames will execute in 2 seconds
settingsBackground.animationDuration = 2.0;
// repeat the annimation forever
settingsBackground.animationRepeatCount = 0;
// start animating
[settingsBackground startAnimating];

Error: Variable-sized object may not be initialized. But why?

enter code hereint quantity = [array count];
int i;
for (i=0; i<quantity; i++)
{
NSString *imageName = [NSString stringWithFormat:#"Car_%#.jpg", [[array objectAtIndex:i] objectForKey:#"CarName"]] ];
UIImage *img[i] = [UIImage imageNamed:imageName];
UIImageView *imgView[i] = [[UIImageView alloc] initWithImage:img[i]];
imgView[i].frame = CGRectMake(i*kWidth, 0, kWidth, kHeight);
[scrollView addSubview:imgView[i]];
[imgView[i] release];
}`enter code here`
Error: Variable-sized object may not be initialized. But why?
UIImage *img[i] = [UIImage imageNamed:imageName];
This declares a C-style array of size i and attempts to initialise it with an instance of UIImage. That doesn't make sense. What are you trying to do? Where is the rest of your code?
Edit:
Okay, I think I see what you are doing. Just get rid of all the places you have [i]. Inside the loop, you are only dealing with one item at a time, and even if you weren't, that's not how you use arrays.
You may want to try this:
int i;
for (i=0; i<quantity; i++)
{
NSString *imageName = [NSString stringWithFormat:#"Car_%#.jpg", [[array objectAtIndex:i] objectForKey:#"CarName"]] ];
UIImage *img = [UIImage imageNamed:imageName];
UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
imgView.frame = CGRectMake(i*kWidth, 0, kWidth, kHeight);
[scrollView addSubview:imgView];
[imgView release];
}
You don't need to use img[i] in order to populate a scrollview with UIImageView.