How to reuse a UILabel? (Or any object) - iphone

This:
UILable *myLabel = [[UILabel alloc] init];
UILable *myLabel = [[UILabel alloc] init];
gives me a redefinition error.
But this:
for(i=0;i<5;i++)
{
UILable *myLabel = [[UILabel alloc] init];
// some label code here
[self.view addSubview:myLabel];
[myLabel release];
}
doesn't. So is the second one false? Should I define it before and just reuse it?
Is that right:
UIIMageView *Sign;
//Some Sign Stuff
Sign = [[UIImageView alloc]init];
Sign.image = [UIImage imageNamed:#"Minus.png"];
frame = CGRectMake(160 ,80, 64, 64);
Sign.frame = frame;
[scrollView addSubview:Sign];
Sign = nil;
[Sign release];
//Some other Sign stuff
Sign = [[UIImageView alloc]init];
Sign.image = [UIImage imageNamed:#"Plus.png"];
frame = CGRectMake(200 ,80, 64, 64);
Sign.frame = frame;
[scrollView addSubview:Sign];
Sign = nil;
[Sign release];
is that correct? That doesnt work without the Sign = nil. So it seems a little wobbly too.

You cannot have identical variable names used in the same block level scope. So in your first example you cannot have a variable definition with the same name, you have to name them differently.
- (void) method {
UIImageView* image1;
// here we define a new block scope. This can be a block of any kind (while, for, if)
{
// All reference in this block to this variable will see this definition.
UIImageView* image1;
// Using image1 here
}
// Here we see again the image1 defined at the beginning of the method.
}
In your loop example you are in a new scope that it's reinitialize after each iteration.
Your third example is correct in that it define the variable only one time. You reuse this variable after that to assign a new object. The third one is less elegant in that your variable name does not describe well for each case what are their purpose.
For your case of 'Sign = nil' this effectively make the line that follows useless since in Objective-C a message sent to a nil object is ignored.
I would suggest to define a method that you can call to create your images that look the same. Something like:
- (void) createImage:(NSString*) image frame:(CGRect) frame {
UIImageView *Sign;
Sign = [[UIImageView alloc]init];
Sign.image = [UIImage imageNamed:image];
Sign.frame = frame;
[self.scrollView addSubview:Sign];
[Sign release];
}

Your for-loop is perfectly fine. The scope of myLabel is limited to one run of your for-loop. So each run a new variable to hold the reference to your UILabel is created.
The second code you posted has leaks.
Sign = nil
[Sign release]
This will release the object at address nil and not the object you created. I can't see what else is wrong with your code, but your fix is definitely not fixing the root cause. Maybe it will help to post what error/warning you get when removing Sign = nil.
Also note that starting your variable names with a capital letter is not a good naming convention, because usually class names start with one.

Related

How to get instance variable name?

I created a simple iPhone screen with two UIButtons programmatically like below.
- (void)viewDidLoad
{
[super viewDidLoad];
UIButton *buttonOne = [UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonOne.frame = CGRectMake(60, 70, 200, 40);
[buttonOne setTitle:#"One" forState:UIControlStateNormal];
[self.view addSubview:buttonOne];
UIButton *buttonTwo = [UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonTwo.frame = CGRectMake(60, 250, 200, 40);
[buttonTwo setTitle:#"Two" forState:UIControlStateNormal];
[self.view addSubview:buttonTwo];
}
Now on press of button with title as "One", I want to get the variable name as "buttonOne", similarly on press of button with title as "Two" I want to get the variable name as "buttonTwo".
I am not finding a way to get the variable name. Any help? Thanks in advance
First off I'd like to disclaim that this is not good coding style. I assume you're doing this because of some special/unique case, or as a proof of concept. In a production app, this is NOT the way to go. You should set your buttons as properties/ivars and you can compare them when they're pressed, or you can assign tags, or separate targets/selectors for each button. Anything you can do to avoid this approach is good because to be honest this approach is kind of terrible (see note at the end of next paragraph about nil/0 values).
You can check out this code below from a previous SO answer - it will return the name of the ivar given the pointer. However, you have to declare your buttons as ivars and not local variables. Also, if two ivars are nil, it will report the same. So this will only work if all your object ivars are not nil, and your primitive type ivars are not 0.
#import <objc/objc.h>
- (NSString *)nameOfIvar:(id)ivarPtr
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([self class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
id pointer = object_getIvar(self, ivar);
if(pointer == ivarPtr)
{
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
break;
}
}
free(ivars);
}
return name;
}
So add a method buttonPressed: as follows:
- (void)buttonPressed:(id)sender {
if ([sender isKindOfClass:[UIButton class]]) {
NSString *buttonName = [self nameOfIvar:sender];
//Now you can do something with your button name
}
}
Source of first block of code: Get property name as a string
we can get only button title name by this way buttonOne.titleLabel.text.
we can't get variable name

Sending messages to released objects?

I have several UIView subclasses (buttons, labels, etc.) that follow the following setup pattern. My question is, why are messages still able to be sent to the UILabel after release?
myLabel = [[UILabel alloc] initWithFrame:someFrame];
[someUIView addSubview:myLabel];
[myLabel release];
myLabel.textAlignment = UITextAlignmentCenter;
// other property changes to myLabel
They are "owned" by a new UIView, I suppose, but I don't understand why release doesn't destroy the original object and thereby all messages to it. I'm not making property changes through someUIView's subViews. I'm not complaining. I'm just trying to understand why.
EDIT: I should add that these are instance variables, if that makes a difference.
The object is not destroyed as long as the retain count is greater than 0. In this case someUIView has retained the object.
It is really best not to access an object after releasing it. a better pattern might be:
myLabel = [[[UILabel alloc] initWithFrame:someFrame] autorelease];
myLabel.textAlignment = UITextAlignmentCenter;
[someUIView addSubview:myLabel];
myLabel = nil;
Second example:
myLabel = [[UILabel alloc] initWithFrame:someFrame];
[someUIView addSubview:myLabel];
myLabel.textAlignment = UITextAlignmentCenter;
// other property changes to myLabel
[myLabel release];
myLabel = nil;
Your call to -addSubview: calls -retain on the label when it receives it. At this point, you relinquish ownership (by calling -release) and only the view owns it. But it still exists until the containing view also releases it.
You can still send messages to the label because the label hasn't been released yet. -addSubview: retains the objects passed in, so the object remains in memory since the view is still holding a reference and you didn't nil the myLabel pointer.
Because they are probably retained before...

Incorrect decrement warning from Xcode from my class method

I created a class to take care of my UILabels in 1 line instead of taking 4-5 by doing...
+(UILabel*)BeautifyLabel:(UILabel *)label withText:(NSString *)message withFont:(NSString *)font andSize:(float)size andColor:(UIColor *)theColor{
label.backgroundColor = [UIColor clearColor];
label.textColor = theColor;
label.font = [UIFont fontWithName:font size:size];
label.text = message;
return label;
}
And to call it, i do
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake....];
label = [CommonMethods BeautifyLabel:label withText:#"hi" withFont:#"Helvetica" andSize:13 andColor:[UIColor whiteColor]];
[self.view addSubview label];
[label release];
The analyzer probably doesn't like the part where I pass the label to my CommomMethods class, but since i'm initializing and releases the label in the current controller and the CommonMethods class doesn't do anything memory related, this is safe, right?
Also, would this be cause for Apple to reject my app?
Thanks
Your BeautifyLabel method should not return the label pointer. That is probably what the analyzer is complaining about (but it would be nice to see the text of the analyzer error).
The analyzer is assuming that BeautifyLabel method is returning a new instance of the label which then overwrites the one you had in label variable thus causing a memory leak of the overwritten instance (and overreleasing of the returned instance).
In the code:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake....];
label = [CommonMethods BeautifyLabel:label withText:#"hi" withFont:#"Helvetica" andSize:13 andColor:[UIColor whiteColor]];
label is allocated on the first line, on the second line label is replaed by the call to BeautifyLabel or so the analyzer thinks, not knowing what is done in BeautifyLabel. It can't assume you are returning the same object.
Either do not make the assignment:
[CommonMethods BeautifyLabel:label withText:#"hi" withFont:#"Helvetica" andSize:13 andColor:[UIColor whiteColor]];
or use different label pointer names:
UILabel *labelTemp = [[UILabel alloc] initWithFrame:CGRectMake....];
label = [CommonMethods BeautifyLabel:labelTemp withText:#"hi" withFont:#"Helvetica" andSize:13 andColor:[UIColor whiteColor]];

Object type changes

I'm having a bit of an issue with holding a mixture of a custom class and UIImage views in an array. These are stored in the array and I'm using:
if ([[fixtures objectAtIndex:index] isKindOfClass:[Fixture class]])
to distinguish between if it's a UIIMage or Fixture object. My source code for this is:
- (void) moveActionGestureRecognizerStateChanged: (UIGestureRecognizer *) recognizer
{
switch ( recognizer.state )
{
case UIGestureRecognizerStateBegan:
{
NSUInteger index = [fixtureGrid indexForItemAtPoint: [recognizer locationInView: fixtureGrid]];
emptyCellIndex = index; // we'll put an empty cell here now
// find the cell at the current point and copy it into our main view, applying some transforms
AQGridViewCell * sourceCell = [fixtureGrid cellForItemAtIndex: index];
CGRect frame = [self.view convertRect: sourceCell.frame fromView: fixtureGrid];
dragCell = [[FixtureCell alloc] initWithFrame: frame reuseIdentifier: #""];
if ([[fixtures objectAtIndex:index] isKindOfClass:[Fixture class]]) {
Fixture *newFixture = [[Fixture alloc] init];
newFixture = [fixtures objectAtIndex:index];
dragCell.icon = [UIImage imageNamed:newFixture.fixtureStringPath];
[newFixture release];
} else {
dragCell.icon = [fixtures objectAtIndex: index];
}
[self.view addSubview: dragCell];
}
}
However, when dragging the cell that was an object of class Fixture, I would get errors such as EXC_BAD_ACCESS or unrecognized selector sent to instance (which makes sense as it was sending a CALayerArray a scale command.
I therefore set a breakpoint to see inside the fixtures array. Here I saw that the UIImages were all set to the right class type but there was also:
(CALayerArray *)
(Fixture *)
(NSObject *)
for the positions were the Fixture classes were being held in the array. Could anyone shed some light onto why it's doing this? If you need any more info to help please feel free to ask.
Denis
In your code here:
Fixture *newFixture = [[Fixture alloc] init];
newFixture = [fixtures objectAtIndex:index];
dragCell.icon = [UIImage imageNamed:newFixture.fixtureStringPath];
[newFixture release];
It looks like you're releasing an autorelease object (newFixture). When you get an object out of the array, it's autorelease.
You also have a memory leak, when you allocate the newFixture at the first line, that object is never released because you replace the pointer to it in your 2nd line.
Fixture *newFixture = [[Fixture alloc] init]; // THIS OBJECT IS NEVER RELEASED
newFixture = [fixtures objectAtIndex:index]; // YOU'RE REPLACING THE newFixture POINTER WITH AN OBJECT FROM THE ARRAY
dragCell.icon = [UIImage imageNamed:newFixture.fixtureStringPath];
[newFixture release]; // YOU'RE RELEASING AN AUTORELEASED OBJECT
So the code should be like
Fixture *newFixture = [fixtures objectAtIndex:index];
dragCell.icon = [UIImage imageNamed:newFixture.fixtureStringPath];
Then your property should retain the image correctly.

Views load slowly when switching between views on 3G iPhones - how to change my style?

In my iPhone app, views will often load slowly when transitioning, like if a user clicks a button on the Tab Bar Controller. This happens more if the phone is low on memory. It doesn't really come up on 3GS phones, but it's a big problem on 3G phones.
I suspect that I'm not following some best practices for creating UIViewControllers. I think I might be doing too much in the init functions, not using the viewDidLoad function, or something. It seems to affect all my views, so I think it's a problem with my style in general, not some particular snippet.
Can anyone tell me what i might be doing wrong? Here is some sample code, from a UIViewController subclass:
EDIT: In response to the question: "where is this being called?"
This function gets called in this case when the user clicks a marker on the map:
if(marker.label.tag == SavedBookmarkTag) {
SavedDetailScreen *savedBookmark = [[[SavedDetailScreen alloc] initBookmarkView:
[(NSDictionary *)marker.data objectForKey:#"bookmark"]]autorelease];
[savedBookmark showMap];
[self.navBar pushViewControllerWithBackBar:savedBookmark];
return;
}
END EDIT
-(id)initBookmarkView: (Bookmark *)bm {
self = [self initView];
self.bookmark = bm;
primaryLabel.text = [bm title];
secondaryLabel.text = [self getLineWithLat:[bm lat] AndLon:[bm lon] AndDate:[bm timeCreated]];
return self;
}
- (id)initView {
self = [super init];
self.isWaypoint = NO;
UIImageView *bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"238-beveled-background.png"]];
bg.frame = CGRectMake(0, 0, 320, 376);
[self.view addSubview:bg];
bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"small-label.png"]];
[self.view addSubview:bg];
[bg release];
self.primaryLabel = [[UILabel alloc]init];
primaryLabel.font = TITLE_FONT;
primaryLabel.backgroundColor = [UIColor clearColor];
primaryLabel.textColor = LIGHT_BLUE;
self.secondaryLabel = [[UILabel alloc]init];
secondaryLabel.font = TEXT_FONT;
secondaryLabel.backgroundColor = [UIColor clearColor];
secondaryLabel.textColor = LIGHT_BLUE;
secondaryLabel.lineBreakMode = UILineBreakModeClip;
self.thirdLabel = [[UILabel alloc]init];
thirdLabel.font = TEXT_FONT;
thirdLabel.backgroundColor = [UIColor clearColor];
thirdLabel.textColor = LIGHT_BLUE;
thirdLabel.lineBreakMode = UILineBreakModeCharacterWrap;
[self.view addSubview:primaryLabel];
[self.view addSubview:secondaryLabel];
[self.view addSubview:thirdLabel];
self.loadingBackground = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"stats-box.png"]];
loadingBackground.frame = CGRectMake(0, 115, loadingBackground.frame.size.width, loadingBackground.frame.size.height);
[self.view addSubview:loadingBackground];
[self.view sendSubviewToBack:loadingBackground];
AnimatedGif *animatedGif = [[[AnimatedGif alloc] init] autorelease];
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"35" ofType:#"gif"]];
[animatedGif decodeGIF: data];
UIImageView *loadingImage = [animatedGif getAnimation];
loadingImage.frame = CGRectMake(150,150,loadingImage.frame.size.width,loadingImage.frame.size.height);
[loadingImage startAnimating];
[loadingBackground addSubview:loadingImage];
[loadingImage release];
[self layoutSubviews];
return self;
}
- (void) layoutSubviews {
self.view.frame = CGRectMake(0,0,320,372);
primaryLabel.frame = CGRectMake(30, 30, 260, 18);
secondaryLabel.frame = CGRectMake(30 ,52, 260, 16);
thirdLabel.frame = CGRectMake(30, 72, 260, 16);
}
The slowness that you're seeing can probably be attributed to the fact that constructing objects and reading data from the flash are both expensive processes. Look for opportunities to reuse existing objects rather than constructing them multiple times, and consider deferring especially expensive operations until after the view gets displayed.
In this case, I would start with a couple changes:
Make savedBookmark a member variable so that you can construct it once and reuse it. Replace your initBookmarkView: method with a setBookmarkView: method that you can call after this member variable is constructed to reconfigure your labels for the specific bookmark being displayed.
Take the subview creation code out of initView and put it in loadView. This is the most appropriate place to construct your own view hierarchy programmatically. UIViewController implements lazy loading on its view property to defer construction as long as possible. The view property is nil until the first time it's requested. At that point UIViewController calls loadView to set the property. The default implementation loads the view from a nib file if one is defined. Otherwise it just constructs an empty UIView and makes that the main view. Note that you'll have to construct the container view and set the view property yourself.
In other apps you may get some improvement by moving some initialization code into viewDidLoad:, which gets called after the view property is loaded, whether programmatically or from a nib. If you ever have an especially slow operation like loading images from a remote URL, you might want to start loading the data asynchronously in viewDidLoad:, and then update your subviews once the data finishes loading. In some cases you may also want to defer some code until viewDidAppear:. Just be aware that this method gets called every time the view appears, unlike loadView and viewDidLoad:, which only get called once.