dynamic UILabel in UIView increase memory - ios

I have an UILabel pinter in my UIView (using ARC).
I dynamically create a lot of text and override the the same pointer every time.
I thought, that if I use the same pointer all the time, and override it with the new objects, they still be in my View, but the pointer of them will be deallocated. But as I see, my memory increase all the time, if the text was changed and the drawRect executed. Maybe someone know the better way to do that, or to fix this memory issue.
UPDATE: Code
#interface Bars : UIView{
NSMutableDictionary *dictCopy;
UILabel *pivotLabel;
}
for (a lot of times) {
pivotLabel = [[UILabel alloc] initWithFrame:frame];
pivotLabel.text = pivotText;
pivotLabel.backgroundColor = [UIColor clearColor];
pivotLabel.textColor = self.color;
[self addSubview:pivotLabel];
}

When you add a new label as a subview the parent view retains it. Nilling a pointer is not enough to remove it. To remove a label, do this:
[self.myLabel removeFromSuperview];
self.myLabel = nil;

Related

iOS release memory issue

I've got a problem when I define a UILabel add it to a UIView
UIView *dwView=[[UIView alloc] initWithFrame:CGRectMake(20, 50, 975.0, 620)];
UILabel *label1 = [[UILabel alloc]initWithFrame:CGRectMake(10.0, 160.0, 950.0,
170.0)];
# i add many UILabel in dwView
...
UILabel *label1
UILabel *label2
UILabel *label3
...
dwView.addView(lable1);
dwView.addView(lable2);
dwView.addView(lable3);
dwView.addView(...);
[lable1 release];
[lable2 release];
[lable3 release];
[... release];
No matter where I define the label, I release it with the method:
[lable1 release];
lable1 = nil;
I log the retainCount, its all 0, but I checked the memory with the profile->allocations it's still not reducing.
I want to know why it is like this, and how I can reduce the memory.
edit 1: I built my project with ARC
edit 2:
now I define variable in .h
{
UIView *dwView;
UILabel *label1,lable2;
}
Init in .m
{
dwView=[[[UIView alloc] initWithFrame:CGRectMake(20, 50, 975.0, 620)] autorelease];
label1 = [[[UILabel alloc]initWithFrame:CGRectMake(10.0, 160.0, 950.0, 170.0)] autorelease];
label1.text = wordString;
dwView.addView(lable1);
}
-(void)dealloc {
[super dealloc];
label1 = nil;
dwView = nil;
}
I try the code above, it doesn’t work.
So: how can i release the variable dwView and lable1
edit
The short answer: Use ARC.
If you're determined to use manual reference counting, a few things:
Looking at retainCount is not useful. It will just confuse you. (You can't see pending autoRelease calls)
In your code, you are creating label1 as a local variable. Then you show ..., meaning you have code somewhere else. If you define a new local variable label1 (and you switch to "lable1" (different spelling) it will be nil. If you want to be able to get to label1 to add it to a superview and/or release it, you either need to do it in the same method/scope, or make it an instance variable.
Another option would be to autoRelease your label after creating it, then add it to the view you want to add it to. That will only work if you add it to your view before the current autoRelease pool is drained.
Your issue is using Allocations. It doesn't accurately tell you how much live memory you're using. For that you should be using Activity Monitor.
It's better to use ARC. Otherwise use autoRelease at the end of your declaration. For example,
UILabel *label1 = [[[UILabel alloc]initWithFrame:CGRectMake(10.0, 160.0,
950.0,170.0)]autorelease];
otherwise release those with
dealloc() label = nil;

UIImageView automatically removing itself from self.view

tutorialImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Tap to Start.png"]];
tutorialImage.frame = CGRectMake(0, 0, 1024, 768);
[tutorialImage addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(blankMethod)]];
tutorialImage.userInteractionEnabled = YES; // i use this line and the previous line so that the user can't press any buttons behind the image
tutorialImage.alpha = 0;
[self.view addSubview:tutorialImage];
[self.view bringSubviewToFront:tutorialImage];
[UIView animateWithDuration:1.0f animations:^{
tutorialImage.alpha = 1;
} completion:^(BOOL finished) {
[self.view addSubview:tutorialImage]; // this line makes the image come back
}];
I know you probably won't be able to deduce the problem just from this code, but is there anything in that code that makes the tutorialImage auto remove itself from it's superview?
Anyway, during the UIView animation the image fades in for a bit like normal, then it disappears. If I add that last line of code there (the commented one), the UIView animation will make the image fade in and flash once halfway through. I just added this image and there is no code telling it to remove itself from superview.
Let me know if you have any ideas as to fixing the problem or showing you more code, I'll check frequently.
Also, I've tried restarting the simulator which didn't work, and the tutorial image is declared in the h file UIImageView *tutorialImage;. The console doesn't show any errors or anything when the problem occurs or anything.
Edit:
Ok, strange. I altered the declaration in the H file from UIImageView *tutorialImage; to #property (strong, nonatomic) UIImageView *tutorialImage; then used _tutorialImage fixed the problem. Is this something to do with the strong parameter? I'll mark who ever can explain what was going on as correct.
When you have a weak reference, ARC will dealloc the object once there are no more retains on it (when no object is pointing at the object with a strong pointer). When you changed the #property to strong, you are now telling ARC to keep the object around until the parent (your view controller) is dealloc'ed.

Programatically generated UILabel origin point incorrectly to (0.0) set on first load

I am trying to programatically generate two UILabels in my application for each UIImageView on my storyboard. The code runs and works correctly, however, on first load the two UILabels form in the (0.0) coordinate of the main view, as opposed to the UIImageView frame origin.x,origin.y. I can't understand why this is happening.
If I then click on a different tab and return to the page, the labels generate in the correct location.
Why is this? How can I get it to initially generate the labels in the correct location?
-(void) viewWillAppear:(BOOL)animated
{
//removed unneccessary code above...
int i = 0;
for (UIImageView *plantScreen in self.view.subviews)
{
if ([plantScreen isMemberOfClass:[Plant class]])
{
#try
{
//the label which will hold the name
UILabel *plantName = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height), 160, 30.0)];
plantName.numberOfLines = 1;
plantName.minimumScaleFactor = .5;
plantName.adjustsFontSizeToFitWidth = YES;
[plantName setTextAlignment:NSTextAlignmentCenter];
[plantName setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:plantName];
plantName.hidden = false;
[self.view bringSubviewToFront:plantName];
//create the label which will hold the quantity
UILabel *quantity = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height + 20), 160, 30.0)];
[quantity setTextAlignment:NSTextAlignmentCenter];
[quantity setBackgroundColor:[UIColor clearColor]];
quantity.text = [NSString stringWithFormat:#"%d",plant.quantity];
[self.view addSubview:quantity];
quantity.hidden = false;
[self.view bringSubviewToFront:quantity];
i++;
}
#catch (NSException *exception)
{
NSLog(#"An exception occured: %#", [exception reason]);
}
#finally
{
}
}
}
}
Frame of the UIImageView depends on the image being drawn and its contentMode property. You can try setting the contentMode to UIViewContentModeScaleAspectFill to see if it forces to keep its assigned frame.
First things first, you're missing a call to [super viewWillAppear:animated]. You need to give the superclass (including the UIViewController base class) a chance to "do its magic".
Never forget about giving the parent class a chance to do its magic, unless you know really really well what you're doing.
Second, UI creation should be done in -loadView, not in viewWillAppear:.
Try these two things first.
Alright. Now I'm curious about how you moved things to -loadView. Did you add [super loadView];?
In fact, now that I think about it, moving to -loadView is wrong in this case; you obviously instantiate some views through a nib. UIViewController's implementation of -loadView typically just loads the nib file. Once that's done, UIViewController's -loadView calls -viewDidLoad.
So when you're not creating all UI programmatically but are instead allowing UIViewController to load it from nib, you actually probably want to move code into -viewDidLoad. (See template generated by Xcode when you tell it to create a new UIViewController subclass.)
Moving on, let's consider what the frame depends on. It depends on some view class you called Plant.
Please don't call it that way; it's confusing. Call it PlantView, so a casual reader of your code is aware of what the class is supposed to do. Similarly, you might want to call the variables plantView instead of plantScreen, and plantNameLabel instead of plantName. plantScreen implies a variable containing UIScreen, and plantName implies an NSString more than it implies a UILabel. Same applies to quantity; call this variable quantityLabel.
Next, let's consider what the variables are depending on -- their origin's x and y do not change based on the counter, variable i. Perhaps you meant to write:
UILabel *plantName = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height * i), 160, 30.0)];
and later on:
UILabel *quantity = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height*i + 20), 160, 30.0)];
Next, avoid exceptions and exception handling. Ensure that the exception does not occur via other forms of checking; Apple highly recommends you fix exceptions while writing the application and not handle them when they run:
Important: You should reserve the use of exceptions for programming or
unexpected runtime errors such as out-of-bounds collection access,
attempts to mutate immutable objects, sending an invalid message, and
losing the connection to the window server. You usually take care of
these sorts of errors with exceptions when an application is being
created rather than at runtime.
Next, a small stylistic remark (not very important): you're mixing calling setters via properties and calling setters directly. Nothing wrong (they end up doing exactly the same), but stylistically not very nice.
Next, unless you're using ARC (automatic reference counting), don't forget to release the views once they're added as subviews.
Next, plantScreen (which I named plantView below) can have type set to Plant (which I named PlantView below) when declared inside the loop.
Last but highly important and extremely easy to miss: you call the function isMemberOfClass: instead of isKindOfClass:.
Reworked version of your code (untested):
-(void)viewDidLoad
{
[super viewDidLoad];
int i = 0;
for (PlantView *plantView in self.view.subviews)
{
if ([plantView isKindOfClass:[PlantView class]])
{
//the label which will hold the name
CGRect plantNameLabelFrame = CGRectMake((plantScreenView.frame.origin.x),
(plantScreenView.frame.origin.y + plantScreen.frame.size.height*i),
160,
30.0);
UILabel *plantNameLabel = [[UILabel alloc] initWithFrame: plantNameLabelFrame];
plantNameLabel.numberOfLines = 1;
plantNameLabel.minimumScaleFactor = .5;
plantNameLabel.adjustsFontSizeToFitWidth = YES;
plantNameLabel.textAlignment = NSTextAlignmentCenter;
plantNameLabel.backgroundColor:[UIColor clearColor];
[self.view addSubview:plantNameLabel];
[plantNameLabel release];
//create the label which will hold the quantity
CGRect quantityLabelFrame = CGRectMake((plantScreenView.frame.origin.x),
(plantScreenView.frame.origin.y + plantScreen.frame.size.height*i + 20),
160,
30.0);
UILabel *quantityLabel = [[UILabel alloc] initWithFrame: quantityLabelFrame];
quantityLabel.textAlignment = NSTextAlignmentCenter;
quantityLabel.backgroundColor = [UIColor clearColor];
quantityLabel.text = [NSString stringWithFormat:#"%d", plant.quantity]; // NB: what's "plant"?
[self.view addSubview:quantityLabel];
i++;
}
}
}
I've also removed .hidden = false (which should actually read .hidden = NO; this is Objective-C, and not C++), and bringSubviewToFront: (it's already in front, having just been added by addSubview:).

UIView: layoutSubviews vs initWithFrame

When subclassing UIView, I usually place all my initialisation and layout code in its init method. But I'm told that the layout code should be done by overriding layoutSuviews. There's a post on SO that explains when each method gets called, but I'd like to know how to use them in practice.
I currently put all my code in the init method, like this:
MyLongView.m
- (id)initWithHorizontalPlates:(int)theNumberOfPlates
{
self = [super initWithFrame:CGRectMake(0, 0, 768, 1024)];
if (self) {
// Initialization code
_numberOfPlates = theNumberOfPlates;
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.frame];
[scrollView setContentSize:CGSizeMake(self.bounds.size.width* _numberOfPlates, self.bounds.size.height)];
[self addSubview:scrollView];
for(int i = 0; i < _numberOfPlates; i++){
UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:#"a1greatnorth_normal_%d.jpg", i+1]];
UIImageView *plateImage = [[UIImageView alloc] initWithImage:img];
[scrollView addSubview:plateImage];
plateImage.center = CGPointMake((plateImage.bounds.size.width/2) + plateImage.bounds.size.width*i, plateImage.bounds.size.height/2);
}
}
return self;
}
It's the usual tasks: setting up the view's frame, initialising an ivar, setting up a scrollview, initialising UIImages, placing them in UIImageViews, laying them out.
My question is: which of these should be done in init, and which of these should be done in layoutSubviews?
Your init should create all the objects, with the required data. Any frame you pass to them in init should ideally be their starting positions.
Then, within layoutSubviews:, you change the frames of all your elements to place them where they should go. No alloc'ing or init'ing should take place in layoutSubviews:, only the changing of their positions, sizes etc...
In case you're autoresizing works perfectly with just autoresizingFlags, or autolayout, you may just use init to setup the whole view.
But in general your should do layouting in layoutSubviews, since this will be called on every change of the views frame and in other situation, where layout is needed again. Sometimes you just don't know the final frame of a view within init, so you need to be flexible as mentioned, or use layoutSubviews, since you do the layout there after the final size has been set.
As mentioned by WDUK, all initialization code / object creation should be in your init method or anywhere, but not in layoutSubviews.

How do I force a release in iOS5? Or Is there a better way to do this with UILabel?

I am writing a fairly simple application which spawns a thread which ultimately calls the following method to put a UILabel at a certain location. I had expected ARC to clean up the labels as the method closed. I was wrong. :)
Is there a way to force these to be cleaned up or is there something obvious that I am missing? Thanks!
-(void) drawNumberLabel:(NSString *)labelText xloc:(float)xLocation yLoc:(float)yLocation {
UILabel *tempLabel;
tempLabel = [[UILabel alloc] initWithFrame:CGRectMake(xLocation, yLocation, 27.0, 59.0)];
tempLabel.font = [UIFont fontWithName:#"Helvetica" size:fontSize];
tempLabel.text = labelText;
tempLabel.backgroundColor = backgroundColor;
tempLabel.textColor = textColor;
[self addSubview:tempLabel];
}
What do you mean by "clean up the labels"? Are you expecting that tempLabel would be deallocated at the end of this method? It won't, because when you call [self addSubview:tempLabel], your view retains the label. When the superview is deallocated, the labels you've added will also be deallocated.
When you are using ARC (Automatic Reference Counting), you should never make any memory management calls because the compiler will insert these statements for you at compile-time.
The compiler should be injecting [tempLabel release]; to the end of your method at compile-time.
However, because you have added the label as a subview to a view, the containing view will retain the label, and the label will not be released until you remove the label from that view.

Resources