Is there any alternate for animating array of images using UIImageView? - ios

UIImageView has animationImages for animating sequence of images. That works fine. But It holds the images object. So There is a spike in use of memory when this animation is happening. I tried NSTimer for setting image property of that image view, But it doesn't work.
Can we achieve this in any other approach?

Instead of looking for alternatives, just try to correct the existing code.
The best practice of allocating an image for animationImages should be using initWithContentsOfFile instead of imageNamed:
imageNamed: it cache’s your images and you lose control over the memory - there's no guarantee that releasing the object will actually release the image but does provide faster loading of images second time around as they are cached.
imageWithContentsOfFile: it does not cache images and is more memory friendly however as it does not cache images and the heavier images are loaded much slower.
When the animation does stop, just release the image collection array. If you are using ARC then make it the image collection array to nil.
Best Practice:
for(int itemIndex = 0; itemIndex < 20; itemIndex++) {
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"myImage1" ofType:#"png"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
[imageArray addObject:image];
}
After the Animation:
imageArray = nil;

Related

CAKeyframeAnimation - Animating an Array of Images creates a huge allocation after completion

I'm trying to animate an array of UIImage with CAKeyframeAnimation. Easy in theory.
Sample code at the bottom of the post.
My problem is that after the animation did finish, I've got a huge leak that is impossible to get rid of it.
Code to init CAKeyframeAnimation:
- (void)animateImages
{
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
keyframeAnimation.values = self.imagesArray; // array with images
keyframeAnimation.repeatCount = 1.0f;
keyframeAnimation.duration = 5.0;
keyframeAnimation.removedOnCompletion = YES;
CALayer *layer = self.animationImageView.layer;
[layer addAnimation:keyframeAnimation
forKey:#"flingAnimation"];
}
Adding a delegate to the animation and removing the animation manually cause the same leak effect:
... // Code to change
keyframeAnimation.delegate = self;
// keyframeAnimation.removedOnCompletion = YES;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
....
Then:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (flag)
{
[self.animationImageView.layer removeAllAnimations];
[self.animationImageView.layer removeAnimationForKey:#"flingAnimation"]; // just in case
}
}
The result is always a huge allocation. The size of the stack of memory is proportional to the size of the images:
I uploaded an example to GitHub to check the code.
SOLVED
I found the problem.
As gabbler was saying there was not a leak problem. The problem was a high allocation of Images.
I was releasing the array with the images, however, the images did not disappear from memory.
So finally I found the problem:
[UIImage imageNamed:#""];
From method definition:
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method locates and loads the image data from disk or asset catelog, and then returns the resulting object. You can not assume that this method is thread safe.
So, imageNamed: stores the image in a private Cache.
- The first problem is that you can not take control of the cache size.
- The second problem is that the cache did not get cleaned in time and if you are allocating a lot of images with imageNamed:, your app, probably, will crash.
SOLUTION:
Allocate images directly from Bundle:
NSString *imageName = [NSString stringWithFormat:#"imageName.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName
// Allocating images with imageWithContentsOfFile makes images to do not cache.
UIImage *image = [UIImage imageWithContentsOfFile:path];
Small problem:
Images in Images.xcassets get never allocated. So, move your images outside Images.xcassets to allocate directly from Bundle.
Example project with solution here.

UIImageView stops displaying images after a specific amount of loop iterations

My iOS app utilizes a loop to cycle through images in a folder.
My application is supposed to loop through a total of 2031 images (sized 1200x900) inside a folder. The images were taken at 8fps and each image will be displayed as the loop continues to simulate a video clip. After the 696th picture, the images will cease to be displayed in the UIImageView although the app will continue looping.
I tested to see if the disconnect was because of the picture not existing
I started the loop at picture 200, but after picture 896 the UIImageView stop displaying the pictures.
The Code:
imgName = [NSString stringWithFormat:#"subject_basline_mat k (%d).png",jojo];
jojo++;
imageToCrop.image = [UIImage imageNamed:imgName]; //imageToCrop is the name of the UIImageView image and it is set to the image file here
imageToCrop.image = [self imageWithImage:imageToCrop.image convertToSize:self.imageToCrop.frame.size]; //Here the image is converted to fit the bounds of the simulator which is 320x240
The code loops due to a timer that loops it about once every 0.8 seconds.
I ran my code with instruments to see if there was a memory problem occurring,and instruments is very heavy on my computer. As such, my application ran quite slowly. However, when I arrived at the 696th picture, the pictures kept displaying themselves. It was almost as if my application running too quickly caused the picture to not be displayed... which I don't really understand.
The only memory heavy part of the image switching seems to be the size conversion step which is called by the line imageToCrop.image = [self imageWithImage:imageToCrop.image convertToSize:self.imageToCrop.frame.size];
imageToCrop.image = [self imageWithImage:imageToCrop.image convertToSize:self.imageToCrop.frame.size];
The method "imageWithImage" is here:
- (UIImage *)imageWithImage:(UIImage *)image convertToSize:(CGSize)size {
#autoreleasepool {
UIGraphicsBeginImageContext(size);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *destImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return destImage;
}
And the line [image drawInRect:CGRectMake(0, 0, size.width, size.height)]; uses around up the most memory out of all the image management in the app.
Any Ideas as to why my app will only display a certain amount of images?
Try loading the full-size images from the app bundle by URL. For example:
#autoreleasepool {
NSString *imgName = [NSString stringWithFormat:#"subject_basline_mat k (%d)",jojo];
NSURL *imageURL = [[NSBundle mainBundle] URLForResource:imgName withExtension:#"png"];
UIImage *image = [UIImage imageWithContentsOfFile:[imageURL path]];
imageToCrop.image = [self imageWithImage:image convertToSize:self.imageToCrop.frame.size];
}
Almost for sure your problem is [UIImage imageNamed:imgName]. There are hundreds of posts here on the pitfalls of using it. The issue is that it caches the images - its real purpose is for some small number of images in your bundle.
If you have oodles of images, get the path to the image, then get the image through a URL or file pointer. That way its not cached. Note that when you do this, you lose the automatic "get-retina-image-automatically", and so you will need to grab the appropriately sized image depending on whether the device is retina or not.

Image Optimization (iOS)

I'm working on a iPad Magazine and I'm using a lot of images (background, slideshow and animation) and the memory utilized is very high.
I've read the following method uses a lot of memory
UIImage *picture = [UIImage imageNamed:#"myFile.png"];
And they recommended using this one
NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:#"/myFile.png"];
imageView.image = [UIImage imageWithContentsOfFile:fullpath];
But I've found another method as well
imageView.image = [[UIImage alloc] initWithContentsOfFile: [[NSBundle mainBundle] pathForResource:#"myFile" ofType:#"png"]];
In order to optimize my app, which method should I use? All my images are .jpg and were saved for web in Photoshop.
All 3 methods will use the same amount of memory. The differences are the following:
Using [UIImage imageNamed:#"myFile.png"] image is cached in memory for faster reuse. This is good for small images used several times in your application (image background, etc). Cache is removed for non used images when memory warning is received.
Using [[UIImage alloc] initWithContentsOfFile:path] image is not cached and you can "force" release of memory by calling [image release] or setting property to nil using ARC. You have a better management of when memory is released
Using [UIImage imageWithContentsOfFile:fullpath] is just equivalent to [[[UIImage alloc] initWithContentsOfFile:path]autorelease]

Memory Leak Downloading image from server

I have a paged slider view with an image on each page. I'm using NSOperationQueue to help me download the images from the server while the program is running. The NSOperationQueue is used to call the following method,
-(NSData *)imageWith:(NSString *)imageName
{
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:imageName];
NSData *imageData = [NSData dataWithContentsOfFile:imagePath];
if (!imageData) {
imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[[NSString stringWithFormat:#"%#/%#", picsURL,imageName] stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]];
if (imageData) {
[imageData writeToFile:imagePath atomically:YES];
}
}
return imageData;
}
and then I use the main thread to display the downloaded image on the scrollerview:
[self performSelectorOnMainThread:#selector(loadPic:) withObject:[NSArray arrayWithObjects:[self imageWith:[picsNames objectAtIndex:imageView.tag]], [NSString stringWithFormat:#"%d", imageView.tag], nil] waitUntilDone:YES];
which calls the following method:
-(void)loadPic:(NSArray *)imageAndTagArray
{
if (imageAndTagArray.count) {
//loading the image to imageview
UIImageView *imageView = (UIImageView *)[scrollView viewWithTag:[[imageAndTagArray objectAtIndex:1] intValue]];
imageView.image = [UIImage imageWithData:((NSData *)[imageAndTagArray objectAtIndex:0])];
//stopping the indicator
[((UIActivityIndicatorView *)[imageView viewWithTag:ACTIVITY_INDICATOR_TAG]) stopAnimating];
}
}
Everything works fine for the first 60 images, but after that I receive a Memory Warning and after about 100 images the app crashes.
I have been spending so much time on this and I can't figure out what to do. I've used Instruments and it doesn't detect any leak. I've also used Analyze and that did show anything either.
EDIT:
If I replace the imageWith: method definition with the following definition I still get the warnings, where 5.jpg is a local image.
-(NSData *)imageWith:(NSString *)imageName
{
return UIImagePNGRepresentation([UIImage imageNamed:#"5.jpg"]);
}
Let me tell you more about the situation.
When the app starts I have a view with a paged scrollview inside it that contains 9 images per page. the scrollview uses the nsoperationqueue to load images which calls the imageWith: method.
when the user taps on any of the images a second view opens with a full display of the selected image. this second view also has a scroll view that contains the same images as the first view but with full display, meaning 1 image per page.
when you are on the second view and scrolling back and forth the app crashes after loading about 60 images.
It also crashes if say it loads 50 images and then you tap on the back button and go to the first view and then tap on another image and go to the second view and load about 10 images.
It sounds like you're holding too many images in memory. When you open the second view, it's reloading the images again from disk, until you end up with two copies of all the images.
The UIImage class may be able to help you with this memory management. In its reference page, it mentions that it has the capability to purge its data in low-memory situations and then reload the file from disk when it needs to be drawn again. This might be your solution.
However, as you're creating the image from an NSData read from disk, the UIImage will probably not be able to purge its memory - it won't know that your image is simply stored on the disk, so it can't throw away the data and reload it later.
Try changing your "imageWith" method to create a UIImage (via imageWithContentsOfFile) from the file URL on the disk just before it returns, and return the UIImage rather than returning the intermediate NSData. That way, the UIImage will know where on disk its image source came from and be able to intelligently purge/reload it as memory becomes constrained on the device.

Fastest way to load images into a UIView?

i have a UIScrollView which holds different UIView container. Each container has to load six UIImages. When scrolling i need to adjust the contents within the "layoutSubviews" method.
Unfortunately loading these images affects in hiccups when the new container and the images will be created.
I´m loading the images async via "dispatch_async". I also used small 32x32px thumbnails which will be replaced with the full image 256x256px image. Unfortunately its stuttering..
Here´s the code of the "initWithFrame" method of such a UIView.
These UIViews will later be added as a subview to the UIView container.
// load ThumbnailImage and replace it later with the fullImage
NSString* thumbImageName = [self.data.imageName stringByAppendingString:#"_small"];
NSString* fileLocation = [[NSBundle mainBundle] pathForResource: thumbImageName ofType:#"png"];
UIImage* image = [UIImage imageWithData:[NSData dataWithContentsOfFile:fileLocation]];
[self setBackgroundImage:img forState:UIControlStateNormal];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSString* fileLocation = [[NSBundle mainBundle] pathForResource: self.data.imageName ofType:#"png"];
UIImage* image = [UIImage imageWithData:[NSData dataWithContentsOfFile:fileLocation]];
[self setBackgroundImage:img forState:UIControlStateNormal];
});
});
Is this the faster way to load images into a UIView?
How does Apple implemented the really fluid image view inside the "Photo.app" when scrolling between different fullscreen images?
I don´t know if it would make sense to use CoreImage?
Any advice would be great!
Thanks for any help and your time.
Have a nice day.

Resources