UITabBarItem memory leak - ios

I have an issue with this code in iOS9, this codes causes a memory leak each
time it is called. I found this leak in instruments and the iOS function that seems to be leaking is [UITabBarButton initWithImage:selectedImage:label:withInsets:].
UITabBarItem *item0 = [tabBarLibrary.items objectAtIndex:0];
item0.image = [UIImage imageNamed:#"TabBarIcon1.png"];
Anyone else have this issue or have a way to work around it? Basically the code is switching the icon for the tab bar depending on the situation nothing complicated.

I set any existing image to nil before setting the new one.
UITabBarItem *item0 = [tabBarLibrary.items objectAtIndex:0];
item0.image = nil;
item0.image = [UIImage imageNamed:#"TabBarIcon1.png"];
This enables ARC to free up memory used by any existing image.

Related

Memory Warning & Crash ImageIO-jpeg-data

I have created an app and it is (well it was) ready to submit.
The app is basically a flash cards apps for kids where there are 12 cards per page.
It is currently iPad only and when the cards are touched a image view animates out of the card to full screen with a full resolution 2048 x 1536 and it plays a sound of the animal, you tap the full size image and it goes back in the card. Each card has 5 images assigned to it. The app has been working perfectly all though the design period and now that I have loaded all of the animals and I have been testing it and noticed it crashed.
Long story short the memory continually grows by 12MB per image (although each image is around 200-400KB) until it reaches 700MB of Memory and it crashes. This memory never reduces.
I have run the app through the xcode instruments and can see no leaks but have identified in the 'Allocations' that it is the 'ImageIO-jpeg-data' category that is the culprit.
I have spent all evening reading up about this as I have found some promising results however these results have not worked for me so I am assuming that I have misunderstood what has been advised or I have not understood so was hoping that someone could maybe help me out or explain in laymen terms how I can resolve this.
OK so to explain how I have the view contoller set up.
Originally I set up the 5 images for each card in an array but there was a delay so someone on here recommended that I pre load the images in viewDidLoad and then call them when the card/button was touched.
So for example I was using this format in viewDidLoad
domestic100 = [UIImage imageNamed:#"ferret-00"];
domestic101 = [UIImage imageNamed:#"ferret-01"];
domestic102 = [UIImage imageNamed:#"ferret-02"];
domestic103 = [UIImage imageNamed:#"ferret-03"];
domestic104 = [UIImage imageNamed:#"ferret-04"];
and then in the the button pressed I assigned the image to the image view by calling:
domestic01ImageContainer.image = domestic100;
inside a if else look checking if the image number was 1 to 4.
Anyway it worked perfectly (apart form this memeory issue)
I have read online that I would be better off declaring the uiimages using
[UIImage imageWithContentsOfFile:path]
as seen in this post:
UIImage memory not deallocating VM: ImageIO_JPEG_DATA?
So the alterations I have made in my code are:
in my viewDidLoad I now create uiimages as so:
domestic200 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-00" ofType: #"jpg"]];
domestic201 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-01" ofType: #"jpg"]];
domestic202 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-02" ofType: #"jpg"]];
domestic203 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-03" ofType: #"jpg"]];
domestic204 = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"dog-04" ofType: #"jpg"]];
and then in the button pressed call the image in to the imageview using
[domestic01ImageContainer setImage:domestic100];
I am getting the same results - the image are just building up in the same memory and not releasing or deallocating.
I'm no wiz when it comes of objective-c and this is my first real app.
If someone could be kind enough to spare a moment of their time to help me I would be really appreciative.
Thanks in advance.
****** Edit ******
So I have told you how I load my images above, I also in the viewDidLoad give the 12 imageviews a starting point and a hidden alpha.
domestic01ImageContainer = [[UIImageView alloc] initWithFrame:CGRectMake(30, 123, 152, 224)];
domestic01ImageContainer.Alpha = 0;
I then add the subview with:
[self.view addSubview:domestic01ImageContainer];
each imageview is allocated a tap gesture to hide the image at a later point:
UITapGestureRecognizer *domestic01Tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTaped:)];
[domestic01ImageContainer addGestureRecognizer:domestic01Tap];
[domestic01ImageContainer setUserInteractionEnabled:YES];
When the thumb / card is pressed a streamlined version of the button would be:
- (IBAction)domestic01ButtonClicked:(id)sender {
static int imageNumber = 0;
if (imageNumber == 0) {
[domestic01ImageContainer setImage:domestic100];
imageNumber++;
}
else if (imageNumber == 1) {
[domestic01ImageContainer setImage:domestic101];
imageNumber++;
}
else if (imageNumber == 2) {
[domestic01ImageContainer setImage:domestic102];
imageNumber++;
}
else if (imageNumber == 3) {
[domestic01ImageContainer setImage:domestic103];
imageNumber++;
}
else if (imageNumber == 4) {
[domestic01ImageContainer setImage:domestic104];
imageNumber = 0;
}
domestic01ImageContainer.Alpha = 1;
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
domestic01ImageContainer.frame = CGRectMake(0, 0, 768, 1024);
} completion:^(BOOL finished) {
}];
}
The in my imageTapped recognizer to shrink the full size image back to the card I use:
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
domestic01ImageContainer.frame = CGRectMake(30, 123, 152, 224);
} completion:^(BOOL finished) {
domestic01ImageContainer.Alpha = 0;
}];
Which basically put the flash card back from full screen to the size of the card / thumbnail and the sets alpha again to 0.
Like I said it all works but as you have suggested the image is being retained somewhere.
Hope this sheds a bit more light on what could be causing the issue.
I had tried setting the image to nil in after the alpha was set to 0 in the imageTapped but this didn't clear the memory.
[domestic05ImageContainer setImage:nil];
Ok so if anyone else is suffering from this issue then here is the solution that I found that work perfectly.
http://xcodenoobies.blogspot.co.uk/2011/02/best-way-to-use-uiimageview-memory.html
Thanks Anna for your input, it pointed me in the right direct but his was the answer that did it for me.
I was having a similar issue. Memory was being held on abnormally. I had a UIImageView IBOutlet and I was setting its image property with my view controller's UIImage property, which I would set while initializing my view controller. The UIImageView would hold on to the UIImage property of my view controller wrongly even when I would go back to the root view controller.
I fixed the issue by changing the view controller's UIImage property to NSData and setting the UIImageView's image property as follows:
[_previewImageView setImage:[UIImage imageWithData:_previewImageData]];
This resolved the issue of ImageIO-jpeg-data being help on unnecessarily. Hope this helps someone.

TabBar Images not showing in iOS 7.1

I'm developing an app that has iOS 7.1 as its Deployment Target. So far everything works just fine, however there is one huge issue, the tab bar images are not showing up. This is confusing, because they show up when running on the iOS 8 Simulator, I'm using the Xcode 6 beta 1.
I've set up two images. The dimensions are 60x60 and 70x60. So size does not seem to be an issue. I've set the images up in Interface Builder. When logging the tab bar image to the console however, it returns nil. I've then tried setting the images up in code, and using the same method to log it to the console, I now have a memory address for the image, but it still does not show up.
When searching for an answer on Google and stackoverflow, I've found this method:
self.tabBarItem.image = [[UIImage imageNamed:#"IMAGE"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
self.tabBarItem.selectedImage = [[UIImage imageNamed:#"IMAGE"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
NSLog(#"%#", self.tabBarItem.image);
This worked for some users apparently, but not for me. I can not figure this out, I'd be glad if someone could help me.
I have a feeling about what you're doing, but it's still not clear.
You have a tab bar, but you have an outlet to the tab bar item itself.
That's not the way you would do it. You create an outlet to the tab bar, then add custom items with:
UIImage *defaultImage = [UIImage imageNamed:#"ImageName"];
UIImage *selectedImage = [UIImage imageNamed:#"ImageName"];
UITabBarItem *itemZero = [[UITabBarItem alloc] initWithTitle:#"Item One" image:defaultImage selectedImage:selectedImage];
UITabBarItem *itemOne = [[UITabBarItem alloc] initWithTitle:#"Item Two" image:defaultImage selectedImage:selectedImage];
NSArray *items = #[itemZero,itemOne];
[self.tabBar setItems:items animated:animated];

How to change UITabBar image dynamically in iOS

At some point during runtime, I want to change the image for one of the tabs in my UITabBar. Here is what I have tried so far:
[[self.tabBarController.tabBar.items objectAtIndex:1]
setImage:[UIImage imageNamed:#"image-name"]
forState:UIControlStateNormal];
The above gives me a -[UITabBarItem setImage:forState:]: unrecognized selector sent to instance
If I use the setImage method without forState it works, but this method was deprecated in iOS 3.
I tried your answers, but now there's this weird blue line above the UITabBar's UIIMage I changed. Any idea why?
Use image and selectedImage properties:
UITabBarItem *item = [self.tabBarController.tabBar.items objectAtIndex:1];
item.image = [UIImage imageNamed:#"image"];
item.selectedImage = [UIImage imageNamed:#"selected_image"];
Also pay attention on this:
By default, the actual selected image is automatically created from
the alpha values in the source image. To prevent system coloring,
provide images with UIImageRenderingModeAlwaysOriginal.
UITabBarItem extends UIBarItem which has an image property.
Do:
[[self.tabBarController.tabBar.items objectAtIndex:1] setImage:[UIImage imageNamed:#"image-name"]];
Though it would be easier to read and debug as follows:
UITabBarItem *item = self.tabBarController.tabBar.items[1];
item.image = [UIImage imageNamed:#"image-name"];
though the question is very old and the answer by #visput absolutely working but for some one complete newbie like me
In your UITabBarController implementation
if while loading of the UITabBarController you want, put it in viewDidLoad method
UITabBarItem *item = [self.tabBar.items objectAtIndex:ITEM_INDEX];// item index is the tab index which starts from 0
item.image = [UIImage imageNamed:#"image"];
item.selectedImage = [UIImage imageNamed:#"image"];
If you want to change it runtime like on selection of something, put this code in viewDidLayoutSubviews method

Animation Terminating due to Memory even with ARC and imageFilePath

I'm doing a simple animation of png's after my app loads and displays its launch screen. The animation works in the simulator but not on the actual iPhone, where it terminates due to memory pressure (the launch screen does load first though). In the debug, the Memory increases exponentially to like 200MB until it crashes. The instruments show no leaks, All Heap and anonymous Vm 9.61 MB overall. The animation doesn't show at all on the actual iPhone.
I've already stopped using imageNamed to set the animation images and used imageFilePath as suggested by another help topic. It works and the images load on the simulator. I'm just not sure what else to do. Help would be very much appreciated.
In didFinishLaunchingWithOptions:
[self.window makeKeyAndVisible];
self.animationView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0, 320, 480)];
NSArray *animationArray = [[NSArray alloc] initWithObjects:
[UIImage imageFromMainBundleFile:#"/Splash_00000.png"],
[UIImage imageFromMainBundleFile:#"/Splash_00001.png"],
//There's about 150 of these so I'll omit the rest
nil];
self.animationView.animationImages = animationArray;
self.animationView.animationDuration = 5.0f;
self.animationView.animationRepeatCount = 1;
[self.animationView startAnimating];
[self.window addSubview:self.animationView];
[self.window bringSubviewToFront:self.animationView];
In case it's needed, this is the method I'm using that I got from another thread:
+(UIImage*)imageFromMainBundleFile:(NSString*)aFileName
{
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/%#", bundlePath, aFileName]];
}
Putting aside that splash screens aren't recommended, you're not leaking but you're running out of heap (which is why it works on the simulator).
Each of those images is owned by the array and won't be released by ARC until long after the animation has completed. Bare in mind that the PNGs, while compressed on disk, will be uncompressed in memory.
Solutions - there are a couple that spring to mind.
Split the animations into a sequence of discrete phases and ensure
that images are released (using #autoreleasepool) between each phase
More realistically, render the animation as a movie and play that
instead (far less likely to stutter between phases)

NSArray code not being run

I can't figure this one out, hopefully someone can help.
This is the code in my ViewController.h file.
{
NSArray *bgImages;
int currentBackground;
bool hasShrunk;
bool hasMoved;
CGAffineTransform translate;
CGAffineTransform sizeShrink;
CGAffineTransform sizeGrow;
}
Here is some of my code in the ViewController.m file.
- (void)viewDidLoad {
hasMoved = NO;
hasShrunk = NO;
currentBackground = 0;
bgImages = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"WallPaper_01.png"],
[UIImage imageNamed:#"WallPaper_02.png"],
[UIImage imageNamed:#"WallPaper_03.png"],
[UIImage imageNamed:#"WallPaper_04.png"],
[UIImage imageNamed:#"WallPaper_05.png"],
nil];
sizeShrink = CGAffineTransformMakeScale(.25,.25);
sizeGrow = CGAffineTransformMakeScale(1,1);
_myBackground.image = [bgImages objectAtIndex:currentBackground];
}
Here is the issue. This code and the entire project run fine on the simulator, no issues. When I change the output to an actual iPhone device it fails. The code is failing on the allocation of the array called bgImages. If I place a breakpoint on viewDidLoad and step into it line by line, it executes up to the array allocation line and then jumps over it. When it then attempts to execute the line:
_myBackground.image = [bgImages objectAtIndex:currentBackground];
it will fail because the array is empty. I don't understand how this can work in the simulator but not on the actual device. The code does not look incorrect but I am at my wits end trying to understand why it behaves this way. Have I coded it incorrectly?
You're getting a problem because the array doesn't have any elements in it. One possibility is that all of the [UIImage imageNamed:...] calls are returning nil. Break one of these out as a separate variable, e.g.
UIImage *firstImage = [UIImage imageNamed:#"WallPaper_01.png"];
and set a breakpoint on that line to make sure that the image is being initialized correctly. If it isn't, one possible reason is that you have the 2x version (WallPaper_01#2x.png) but not the 1x version, or vice versa.
Edit: As #rdelmar notes, another possible reason is that, since Mac OS X uses (by default) a case-insensitive filesystem but iOS uses a case-sensitive filesystem, you're using the wrong filename, but you're only seeing a problem on iOS because OS X doesn't care that the filename isn't exactly right.
You seem to be loading all images, then just using one. This may lead to memory problems in a real device. What you may want to do is something like this;
bgImages = [[NSArray alloc] initWithObjects:
[#"WallPaper_01.png"],
[#"WallPaper_02.png"],
[#"WallPaper_03.png"],
[#"WallPaper_04.png"],
[#"WallPaper_05.png"],
nil];
...
_myBackground.image =
[UIImage imageNamed:[bgImages objectAtIndex:currentBackground]];
Also, have a look at #rdelmar's comment about case sensitivity.

Resources