iOS addSubview & removeFromSuperview causing memory leaks - iphone

I have the following issue:
I have one instance from class that extends UIViewController class, in this class i am adding UIImage to UIImageView, and then adding the UIImageView to the self.view using this method "addSubview:" [self.view addSubview:imageView] and finally adding the self.view to the window using [[[UIApplication sharedApplication] keyWindow] addSubview:[self view]] , and when i click on a close button i am clearing the self.view (removing the subview "UIImageView") and removing the self.view from the window.
now the issue is when i am using the same instance to add other UIImageView with other image, the first data is not freed from the memory even i have released them and removed them from the superview. the following code illustrates the problem.
- (void) setImageWithURL: (NSString *)url {
NSData * imageData =NSData * imageData =[NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
if (imageData) {
UIImage *image = [[UIImage alloc]initWithData:imageData];
[[[UIApplication sharedApplication] keyWindow] addSubview:[self view]];
[self createViewWithImage:image];
[image release];
}
-(void) createViewWithImage: (UIImage *) image{
[self.view setHidden:false];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:imageView];
[imageView release];
}
// for removing the imageView when i click on close button
-(void) closeView{
for (UIView *subView in self.view.subviews) {
[subView removeFromSuperview];
}
[self.view removeFromSuperview];
}
Note: I have used the xcode instruments to test the memory allocation.
UPDATE:
after more research .. it is not addSubview & removeFromSuperview that make memory leaks.. its NSData. I dont why its not released from the memory. I have made some changes to test if its truly the NSData, i saved the image locally and set the image using two cases:
//Case 1:
NSString *path = [[NSBundle mainBundle] pathForResource:#"image" ofType:#"png"];
NSData * imageData =[[NSData dataWithContentsOfFile:path];
image = [[UIImage alloc]initWithData:imageData];
//Case 2:
NSString *path = [[NSBundle mainBundle] pathForResource:#"image" ofType:#"png"];
image = [[UIImage alloc] initWithContentsOfFile:path];
I have found that case one makes memory leaks, while case 2 is clean.
now UIImage cant take the image without using the NSData, and for some reason NSData is not released .. so how can i fix this problem !!!!

Do you have a:
[super dealloc];
in your deriving class's dealloc?

Related

Save image somewhere and use it in another ViewController (iOS)

I've written some code to make a screenshot of the view. I write that image to the photo library. But the thing is, I want to use that image in an other imageView in another ViewController. How can I save the image somewhere in the app and use it in another ViewController?
My code:
UIView* captureView = self.view;
UIGraphicsBeginImageContextWithOptions(captureView.bounds.size, captureView.opaque, 0.0);
[captureView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGRect cropRect = CGRectMake(0 ,0 ,640,1136);
UIGraphicsBeginImageContextWithOptions(cropRect.size, captureView.opaque, 1.0f);
[screenshot drawInRect:cropRect];
UIImage * customScreenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(customScreenShot , nil, nil, nil);
You first need to save your image on the disk:
NSString *documentDirectory =
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)
objectAtIndex:0];
NSString *pngFilePath = [NSString stringWithFormat:#"%#/myImage.png",documentDirectory];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(customScreenShot)];
[imageData writeToFile:pngFilePath atomically:YES];
And then you can create an UIImageView with the file:
UIImageView *myImageView = [[UIImageView alloc] initWithImage:[UIImage
imageWithContentsOfFile:pngFilePath]];
[self.view addSubview:myImageView];
If you are using Storyboards, you could pass it to the next view controller like so; in the prepareForSegue method ( this relieves you from having to save it to disk ):
Note: the name of the segue is set by you in MainStoryboard.storyboard. - mySegue.
kMySegue is just a key for that. i.e. #define kMySegue #"mySegue"
imageInOtherViewController is a UIImage in the other view controller.
// TheFirstViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
id destinationViewController = [segue destinationViewController];
if ([[segue identifier] isEqualToString:kMySegue]){
if ([destinationViewController respondsToSelector:#selector(setImageInOtherViewController:)]){
[destinationViewController setImageInOtherViewController:[UIImage imageNamed:#"myImage.png"]];
FastCameraViewController *viewController = segue.destinationViewController;
viewController.delegate = self;
}
}
// OtherViewController.h
#interface OtherViewController : UIViewController
{
}
#property (nonatomic, strong) IBOutlet UIImageView *otherViewControllerImageView;
#property (nonatomic, strong) UIImage *imageInOtherViewController;
// OtherViewController.m
#implementation OtherViewController
#synthesize otherViewControllerImageView;
#synthesize imageInOtherViewController;
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[self otherViewControllerImageView] setImage:imageInOtherViewController];
}
To cache your images I recommend using the NSCache class first ie
NSCache* imagesCache = [[NSCache alloc] init];
[imagesCache setName:#"imagesCache"];
[imagesCache setObject:customScreenShot forKey:#"image1"];
the good news is that NSCache accepts any object of type id so you can store images, mp3s, whatever you want.
If you would like to use the phone's file directory (which is more expensive to use than NSCache..) I recommend you use the Library/Caches directory instead of the Documents directory.. so building on iDevzilla's answer:
NSString *documentDirectory =
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)
objectAtIndex:0];

Where should i put my images in my iPhone project?

I'm new to iPhone dev.
Now i have a project and its directory structure looks like this :
Tutorial
\__Tutorial\
\__1.png
\__TutorialViewController.h
\__TutorialViewController.m
\__TutorialViewController.xib
\__Supporting Files\
\__Frameworks\
\__Products\
I tried to use [UIImage imageNamed:#"1.png"] to render an image to the view:
UIImage *image = [UIImage imageNamed:#"1.png"];
imageView.image = image;
[image release];
But the image doesn't shows up.
So i wonder where should i place the images ?
And additionally, if i got lots of images (like 1000 number), what should do ?
Here's my code :
#import "TutorialViewController.h"
#implementation TutorialViewController
#synthesize labelText;
#synthesize imageView;
-(void) click:(id)sender {
NSString *titleOfButton = [sender titleForState:UIControlStateNormal];
NSString *newText = [[NSString alloc]initWithFormat:#"%#",titleOfButton];
// Change Image When Clicking Color Button
if([titleOfButton isEqualToString:#"Blue"]){
NSLog(#"Blue");
imageView.image = [UIImage imageNamed:#"a.png"];
}else{
imageView.image = [UIImage imageNamed:#"b.png"];;
NSLog(#"Else");
}
labelText.text = newText;
[newText release];
}
You should create a folder named Resources and add images there.
UIImage *image = [UIImage imageNamed:#"IMG_0270.PNG"];
imgView_.image = image;
I had used the above code and it works fine on my system. May be you haven't connected the outlet for imgView in the interface builder.
EDIT:
The problem I found is you are not adding it as a subview. You should change your code like this :
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"1.png"]];
NSString *titleOfButton = [sender titleForState:UIControlStateNormal];
NSString *newText = [[NSString alloc] initWithFormat:#"%#", titleOfButton];
// Change Image When Clicking Color Button
if([titleOfButton isEqualToString:#"Blue"]) {
NSLog(#"Blue");
imageView.image = [UIImage imageNamed:#"IMG_0270.png"];
} else {
imageView.image = [UIImage imageNamed:#"b.png"];
NSLog(#"Else");
}
[imageView setFrame:CGRectMake(10, 10, 230, 123)];
[self.view addSubview:imageView];
// labelText.text = newText;
[newText release];
there is no prbm with ur code. but Some time image become corrupt , due to this reason it not show, try some other images , and if image is not added at bundle path add at
project target > build phase > copy bundle Resources
. might be it will help.
You have problem at this statement
[image release];
The easiest way to remember when to use release is NARC keyword :)
Always remember to make release call in 4 cases.
N New
A Alloc
R Retain
C Copy
Try to remove the first line of your method, if you've connected the UIImageView in the xib you don't need to re-alloc it. Moreover call self.imageView.image instead just imageView.image.

Loading image to UIViewController Asynchronously - With Code

This is written in my viewDidLoad method
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *op = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(downloadImage)
object:nil];
[queue addOperation:op];
// The the rest in other methods;
- (void)downloadImage{
NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:person.picURL]];
UIImage* image = [[UIImage alloc] initWithData:imageData] ;
[self showImage: image];
}
I have tried adding the following codes above [self showImage: image]; and i ended up with an exception.
1.) [self performSelectorOnMainThread:#selector(showImage:) withObject:image waitUntilDone:NO];
2.) [self performSelectorInBackground:#selector(showImage:) withObject:image];
// This is the showImage code.
- (void)showImage:(UIImage *)img {
if (img != nil)
{
myImageView = [[UIImageView alloc] initWithImage:img];
myImageView.frame = CGRectMake(20, 20, 132, 124);
[scrollView addSubview:myImageView];
[pictureOfPerson setImage:img];
}
}
I am trying to Asynchronously download the image and cache it. The image gets downloaded Asynchronously but i am not sure if it gets cached.
1.) How to cache the image and use it in when the view loads again
2.) While the image is downloading, what if i click on another view. Then i need to stop the download. How can i write this code. I know that i have to write it in the viewDidDissapear method.
3.) Is my code correct. Have i missed anything or is there a better approach to do this? if so tutorial or some sample code please
I'd use GCD to download the image. It's much simpler to use than NSOperation. Here's an example:
UIImage *personPicture;
personPicture = [self.imageCache objectForKey:person.picURL];
if (!personPicture) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:person.picURL]];
personPicture = [UIImage imageWithData:imageData];
[self.imageCache setObject:imageData forKey:person.picURL];
dispatch_async(dispatch_get_main_queue(), ^{
[self showImage:personPicture];
});
});
}
else {
[self showImage:personPicture];
}
You could use a class property like an NSMutableDictionary to store the UIImage data and associate it with your URL. If it's present, use it, if not, download the image.
-EDIT-
Adding the code to handle caching.
You can use this:SDWebImage

Custom UIView Leaking Memory - iPhone/iPad/iOS App Development

I have a viewcontroller that repeatedly repositions 6 items within a uiscrollview. However, even though I've limited the number of items within the uiscrollview to 6, I'm still leaking memory when i update their position and their image. Can someone let me know if the following code which represents a unit within the uiscrollview is properly coded? startLoad is the method that I call after to reload the image.
#import "ScrollUnit.h"
#implementation ScrollUnit
#synthesize index;
#synthesize ProductToDisplay;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
}
return self;
}
-(void)startLoad
{
[imageview removeFromSuperview];
[imageview release];
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(loadImage)
object:nil];
[queue addOperation:operation];
[operation release];
}
-(void)loadImage
{
NSString *myimageName = [self.ProductToDisplay valueForKey:IMAGEKEY];
NSString *myimageUrl = [NSString stringWithFormat:#"%#/%#",IMAGE_SERVICE,myimageName];
NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myimageUrl]];
UIImage* image = [[[UIImage alloc] initWithData:imageData] autorelease];
[imageData release];
[self performSelectorOnMainThread:#selector(displayImage:) withObject:image waitUntilDone:NO];
}
- (void)displayImage:(UIImage *)image
{
imageview = [[[UIImageView alloc] initWithFrame:CGRectMake(9, 0, 320, 320)]retain];
imageview.contentMode = UIViewContentModeScaleAspectFit;
imageview.backgroundColor = [UIColor whiteColor];
imageview.image = image;
[self addSubview:imageview];
[imageview release];
//[image release];
}
- (void)dealloc {
[super dealloc];
}
#end
Get rid of the retain message on this line and you should be all set:
imageview = [[[UIImageView alloc] initWithFrame:CGRectMake(9, 0, 320, 320)]retain];
The reason why you need to do this is because you already own the object by calling alloc, so at that point, the relative reference count is 2.
Once you invoke addSubview:, passing in the imageview, the reference count gets bumped to 3, then right back down to 2 once you release it on the next line.
So once that object gets sent release in -dealloc, you're still stuck because the reference count is now 1, not 0 as you expected.
There is also another little thing that might bug (or not). You don't release imageview before you assign it in displayImage method. As long as only startLoad is called you are fine but if displayImage is called from the outside then you still leak.
You might want to use a property with retain and then synthesis the getter and setter methods. This way the iOS will release your previous assignment before it retains your new assigned object. That said then you need to release created image view right were you create it and you have to use "self.imageview" in order to make sure that you use the setter (setImageview).

UIImageView animation has long delay on device

I create a 5-frame animation on UImageView and each image is created by [UIImage imageWithContentOfFile:xxx],
however, the animation on simulator is working well but not on device, it usually has ~1 sec delay though the animation duration is 1sec.
This is my code.
NSArray *animations = [[NSBundle mainBundle] pathsForResourcesOfType:#"png" inDirectory:#"ShakeAnimation"];
UIImageView *imgView = [[UIImageView alloc]initWithImage:[UIImage imageWithContentsOfFile:[animations objectAtIndex:0]]];
imgView.tag = 10;
NSMutableArray *animationImgs = [[NSMutableArray alloc] init];
for( NSString *path in animations )
{
[animationImgs addObject:[UIImage imageWithContentsOfFile:path]];
}
[imgView setAnimationRepeatCount:2];
[imgView setAnimationImages:animationImgs];
[imgView setAnimationDuration:1];
[animationImgs release];
and when the view controller recieved shack event, it calls [imgView startAnimation];
Is anyway to preload the images involved in the animation? thanks.
I figured it out!
If you want to preload the UIImageView animation images, just call [imgView startAnimating] after initialization. It seemed to work for me.