uiimage and uibutton release problem? - iphone

I've been coding for a while now in objective-c and am comfortable with it... but one thing eludes me. Memory management. I'm releasing as I think is correct, but this bit of code is throwing a "EXC_BAD_ACCESS" and crashes the app.
When I comment out and DON'T release the button and image, it works fine. The function is called to read through an array of image filenames.
for (x=items_start;x<items_stop;x++) {
UIButton *button;
UIImage *buttonImage;
buttonImage = [UIImage imageNamed:[NSString stringWithFormat:#"%i.png", x]];
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.tag = x;
[button setImage:buttonImage forState:UIControlStateNormal];
[button addTarget:self action:#selector(duplicateImage:) forControlEvents:UIControlEventTouchUpInside];
[viewBasicItems addSubview:button];
[buttonImage release];
[button release];
}
any ideas? Like i said, when I comment out the last two lines (releasing the button and image) it works OK. Is this normal or should I be able to release them?
Note: I have remove a fair bit of other code to show this example in a smaller chunk!

The instance of button is autoreleased:
button = [UIButton buttonWithType:UIButtonTypeCustom];
You're using the convenience method +buttonWithType: instead of an alloc/init pair. So your app will crash here, as well:
[button release];
Either remove that -release statement or use alloc/init to instantiate the button view.
I would recommend you use alloc/init since you're doing all of this stuff inside a for loop. You could be building up a lot of objects in that loop that need to be autoreleased. It's probably better to manually allocate memory and release it.
And do read Apple's memory management guide.

buttonImage = [UIImage imageNamed:[NSString stringWithFormat:#"%i.png", x]];
Your buttonImage object is autoreleased so you must not release it in your function.
From Memory management guide:
You only own objects you created using
a method whose name begins with
“alloc” or “new” or contains “copy”
(for example, alloc, newObject, or
mutableCopy), or if you send it a
retain message.
Edit: As Alex points your button object is autoreleased also.

You have three choices:
Use alloc/init
for example;
NSString *imagePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:[NSString stringWithFormat:#"/%i.png",x]];
buttonImage = [[UIImage alloc] initWithContentsOfFile:imagePath];
and release it after you are done
[buttonImage release];
Or use retain/copy
buttonImage = [[UIImage imageNamed:[NSString stringWithFormat:#"%i.png", x]] retain ];
and release it
[buttonImage release];
Or use Autoreleased objects like you did but do not release it, because they will be released automatically.
You should read Apple memory management guide like others said.

Ok, the basics.
Most methods that create objects return objects that are allocated, then "autoreleased."
The autorelease call adds your object to the "autorelease pool", which means that they will receive a release call next time your app visits the event loop.
When you use auto released objects, you can use them, then forget about them. They get released automatically.
The exception, as others have said, is calls that have "init" or "new" in the name, or calls to "copy" methods. These methods return objects that have not been auto released. The owner of these objects needs to release or autorelease these objects in order for them to be deallocated, and not cause a memory leak.
In your example code, you create your buttonImage and button objects using the calls +imageNamed and +buttonWithType.
These are class calls that return an object of the desired type. Since they do not contain "init" or "new" in their names, the objects that they return are already autoreleased, so you should NOT release them.
You pass the image you create to the button, so the button retains the image. You then pass the button object to your viewBasicItems object with the -addSubview call, so the view retains the button.
Thus, you should not do anything else. The button will retain the image, and the view will retain the button.
I hope that helps.

it is not a good method of using [UIButton ButtonWithType:]..coz you cant relase the object..instead of this jus use
`for (x=items_start;x<items_stop;x++) {
UIButton *button;
UIImage *buttonImage;
buttonImage = [UIImage alloc] initWithContentOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingString:[NSString stringWithFormat:#"/%i.png",x]]];
button = [[UIButton alloc] init];
button.tag = x;
[button setImage:buttonImage forState:UIControlStateNormal];
[button addTarget:self action:#selector(duplicateImage:) forControlEvents:UIControlEventTouchUpInside];
[viewBasicItems addSubview:button];
[buttonImage release];
[button release];
}
`

Fixing in your code as following:
for (x=items_start;x<items_stop;x++) {
UIButton *button;
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.tag = x;
[button setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%i.png", x]]
forState:UIControlStateNormal];
[button addTarget:self action:#selector(duplicateImage:)
forControlEvents:UIControlEventTouchUpInside];
[viewBasicItems addSubview:button];
[button release];
}

Related

toggle UIButton text programmatically with a conditional statement

Here's my code (it's wrapped in an IBAction that is called when the button is pressed):
if (myButton.currentTitle == #"test") {
[myButton setTitle:#"test2" forState:UIControlStateNormal];
}
if (myButton.currentTitle == #"test2") {
[myButton setTitle:#"test" forState:UIControlStateNormal];
}
I want the UIButton text to toggle when pressed (if text = "test" then change to "test2" and when pressed if text = "test2" change to "test").
I do have an IBOutlet connected for myButton and the the IBAction connected to myButton--so I am pretty sure it isn't a problem with my connections.
For some reason this isn't working, I'm sure I am missing something very simple.
use isEqualToString: instead of ==
This is because you lack a control statement that skips the second if when the first one succeeds. When you come into the block with "test", you switch it to "test2", and then the second condition succeeds immediately, and you turn "test2" back into "#test".
You can an an else to fix this, but you can skip the if altogether by using an NSArray that maps the current state to the new state.
// This should be made static, and initialized only once
NSDictionary *nextTitle = [NSDictionary dictionaryWithObjectsAndKeys:
#"test", #"test2", #"test2", #"test", nil];
// This line does the toggling
[myButton setTitle:[nextTitle valueForKey:myButton.currentTitle] forState:UIControlStateNormal];
if ([myButton.currentTitle isEqualToString:#"test"]) {
[myButton setTitle:#"test2" forState:UIControlStateNormal];
}
if ([myButton.currentTitle isEqualToString:#"test2"]) {
[myButton setTitle:#"test" forState:UIControlStateNormal];
}
Hope, this will help you...
Comparing user-visible strings is generally considered bad practice (and becomes tedious when you need to do i18n), especially with string literals since it's vulnerable to typos.
If you're just going to toggle between two states, the easiest thing to do is to use the UIControl.selected property (corresponding to UIControlStateSelected):
// In init
[myButton setTitle:#"test" forState:UIControlStateNormal];
[myButton setTitle:#"test2" forState:UIControlStateSelected];
[myButton setTitle:#"test2" forState:UIControlStateSelected|UIControlStateHighlighted];
// Toggle
myButton.selected = !myButton.selected;
It also makes the code a lot cleaner when you when you decide to toggle the button image/background/text colours too.
Note the slight gotcha: If you don't set the title for UIControlStateSelected|UIControlStateHighlighted it will use the title for UIControlStateNormal when the button is both selected and highlighted (touched).
When comparing strings to each other try using if([str1 compare:str2] == NSOrderedSame)

IBAction used with Dynamically added UIbuttons

Good Evening all!
I have some UIButtons added dynamically into my view and of course I have an IBAction which handles button events.
The problem is: How can I detect which button is pressed if the only thing I know is the (id)sender and the array of buttons?
Buttons are never the same, every button has a different behavior. When I want to use static buttons and connect them through the IB I use something like this :
-(IBAction)doSomething:(id)sender
{
if(sender == button1)
dosomething;
if(sender == button2)
dosomething else;
if(sender == button3)
dosomething3;
}
In my case this does not work because there is no button1, button2, button3 but a MutableArray of buttons which have the same name as they were allocated with it. Button!
I tried using the way above but with no success and i tried also getting the tag of a button but I have nothing to compare it to!
I would really appreciate your help.
sincerely
L_Sonic
PS Dynamicaly means that i am creating the buttons in random time during run time like this
-(void)configActionSheetView
{
buttonView = [[UIView alloc]initWithFrame:CGRectMake(0.0,460, 60, 480)];
[buttonView setBackgroundColor:[UIColor blackColor]];
[buttonView setAlpha:0.6];
for (int i = 0 ;i<[buffButtons count];i++)
{
UIButton *customButton = [buffButtons objectAtIndex:i];
customButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
//UILabel *customLabel = [[UILabel alloc]init];
//[customButton setTag:(i)+11];
[customButton addTarget:self action:#selector(activateBuffEffect:) forControlEvents:UIControlEventTouchUpInside];
[customButton setAlpha:1.0];
customButton.frame = CGRectMake(8.0, 5+(50*i), 44.0, 44.0);
[customButton setTitle:nil forState:UIControlStateNormal];
buttonView.frame = CGRectMake(0, 460, 60, 50+(44*(i+1)));
[buttonView addSubview:customButton];
}
}
this is inside a functions and gets called during run time. the buffButtons is a mutableArray with buttons that gets populated during runtime.
i need a solution like this i cannot get a different eventhandling method for everybutton.
When you was "added dynamically" I assume you mean that they are created from some piece of code. Since all buttons to different things and you know what a certain button should do, why don't you add different actions to different buttons?
UIButton *myCreatedButton = [[UIButton alloc] init];
[myCreatedButton addTarget:self
action:#selector(doSomething:)
forControlEvents:UIControlEventTouchUpInside];
UIButton *myOtherCreatedButton = [[UIButton alloc] init];
[myOtherCreatedButton addTarget:self
action:#selector(doSomethingElse:)
forControlEvents:UIControlEventTouchUpInside];
In the above code the target (set to self) is the class where the method you want to run is found, the action is the method that you want to run and the controlEvent is what should cause the method to run.
If you did it like this you would split the code in different methods like these (you do not need to specify them in the header):
-(void)doSomething:(id)sender {
// do somthing here ...
}
-(void)doSomethingElse:(id)sender {
// do somthing else here ...
}
This way you don't need to know what button was pressed since the correct code would get called anyway. Besides it makes it cleaner if you need to change the code for some of the buttons.
Found it!
-(IBAction)buttonTapped:(id)sender {
UIButton *btn = (UIButton *)sender;
NSLog(#"tapped: %#", btn.titleLabel.text);
[self anotherIBAction:sender];
}
now i can get the tag from the btn :D
thnk you!
Why not add a tag the button and then get the tag number from (id)sender in the selector function?

how to change uibutton title at runtime in objective c?

I know this question is asked many a times,and i am also implementing the same funda for chanding the title of the uibutton i guess.
Let me clarify my problem first. I have one uibutton named btnType, on clicking of what one picker pops up and after selecting one value,i am hitting done button to hide the picker and at the same time i am changing the the title of the uibutton with code
[btnType setTitle:btnTitle forState:UIControlEventTouchUpInside];
[btnType setTitleColor:[UIColor redColor] forState:UIControlEventAllEvents];
But with my surpriaze,it is not changed and application crashes with signal EXC_BAD_ACCESS. I am not getting where i am making mistake.I have allocated memory to the btnType at viewdidLoad. Also I am using
-(IBAction)pressAddType
{
toolBar.hidden = FALSE;
dateTypePicker.hidden = FALSE;
}
event on pressing the button to open the picker. Also i would like to mention that i have made connection with IB with event TouchUpInside for pressAddType.
Any guesses? I will be grateful if you could help me.
Thanks.
UPDATE:
#interface AddSettingPage : UIViewController<UITextFieldDelegate>
{
IBOutlet UIButton *btnType;
NSString *btnTitle;
}
#property (nonatomic, retain) IBOutlet UIButton *btnType;
-(IBAction)pressAddType;//:(id)sender;
Also
#synthesize btnType,btnTitle;
try
[yourButton setTitle:#"your title" forState:UIControlStateNormal];
[yourButton setTitle:#"your title" forState:UIControlStateSelected];
[yourButton setTitle:#"your title" forState:UIControlStateHighlighted];
when the picker is dismissed, the button (which was the control that hold focus) will be in the selected state (or the highlighted .. check it out).
and stop using UIControlEventTouchUpInside in the forState: parameter. it is not a state, it is an event. you are passing an event identifier instead of a state identifier
The state you pass in setTitle should be something like UIControlStateNormal:
[b setTitle:#"" forState:UIControlStateNormal];
Instead of
- (IBAction) pressAddType;
declare
- (IBAction) pressAddType:(id)sender; //or (UIButton *)sender
and define it like:
-(IBAction)pressAddType:(id)sender
{
toolBar.hidden = FALSE;
dateTypePicker.hidden = FALSE;
[(UIButton *)sender setTitle:btnTitle forState:UIControlEventTouchUpInside];
[(UIButton *)sender setTitleColor:[UIColor redColor] forState:UIControlEventAllEvents];
}
As you can see, you don't need to have your button as an ivar because it is passed as a parameter of the method when pressed.
This answer why not button title change only,EXC_BAD_ACCESS error only you getting when an object you trying to access those object is not in memory of stack or that object has null value. So my advice is please check your object (btnTitle) is in memory or not?

How to customize MPVolumeView?

I have tried many methods to implement a regular UISlider and control the device volume, but it's all Native-C functions which results in many untraceable bugs.
I tried the MPVolumeView it works like charm, it even controls the device volume even after you close the app, just like the iPod app.
My question is, is there anyway to customize the MPVolumeView with specific colors and images, just like UISlider?
NOTE: I want a legal method without using private undocumented APIs.
UPDATE
As per #Alexsander Akers answer, since the sub views are hidden in MPVolumeView I had to cycle through subviews, get the UISlider and customize it, here is the code.
IBOutlet UISlider *volumeSlider; //defined in <class.h> and connected to a UISlider in Interface Builder
-(void) viewDidLoad {
....
[self setCustomSlider];
....
}
-(void) setCustomSlider{
MPVolumeView *volumeView = [[[MPVolumeView alloc] initWithFrame:[volumeSlider frame]] autorelease];
NSArray *tempArray = volumeView.subviews;
for (id current in tempArray){
if ([current isKindOfClass:[UISlider class]]){
UISlider *tempSlider = (UISlider *) current;
UIImage *img = [UIImage imageNamed:#"trackImage.png"];
img = [img stretchableImageWithLeftCapWidth:5.0 topCapHeight:0];
[tempSlider setMinimumTrackImage:img forState:UIControlStateNormal];
[tempSlider setThumbImage:[UIImage imageNamed:#"thumbImage.png"] forState:UIControlStateNormal];
}
}
[volumeSlider removeFromSuperview];
[self.view addSubview:volumeView];
}
You could try cycling through its subviews and look for a UISlider subclass?
Since iOS 5.0 you can use UIAppearance on a UISlider, even when part of MPVolumeView.
Anywhere in your codebase:
[[UISlider appearanceWhenContainedIn:[MPVolumeView class], nil] setMinimumTrackImage:[[UIImage imageNamed:#"nowplaying_bar_full.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(5, 25, 5, 25)] forState:UIControlStateNormal];
[[UISlider appearanceWhenContainedIn:[MPVolumeView class], nil] setMaximumTrackImage:[[UIImage imageNamed:#"nowplaying_bar_empty.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(5, 25, 5, 25)] forState:UIControlStateNormal];
[[UISlider appearanceWhenContainedIn:[MPVolumeView class], nil] setThumbImage:[UIImage imageNamed:#"nowplaying_player_nob.png"] forState:UIControlStateNormal];
Here a list of some of the other classes that can be implemented using UIAppearance:
https://gist.github.com/mattt/5135521
There are now ways to accomplish this, simply use:
– setMaximumVolumeSliderImage:forState:
– setMinimumVolumeSliderImage:forState:
– setVolumeThumbImage:forState:
Which are slightly different method names than the ones for the vanilla UISlider.
This prevents you from having to cycle through the views and potentially have something break in the future as Apple changes things.
Answer in Swift:
func customSlider() {
let temp = mpVolView.subviews
for current in temp {
if current.isKind(of: UISlider.self) {
let tempSlider = current as! UISlider
tempSlider.minimumTrackTintColor = .yellow
tempSlider.maximumTrackTintColor = .blue
}
}
}
Result:
Try using a Notification, but it looks like Apple is denying them.
[EDIT]
Try this.

uibutton events with #selector [duplicate]

This question already has an answer here:
Closed 11 years ago.
Possible Duplicate:
Uibutton events
-(void)myButtonclick {
NSString *data=[[NSString alloc]initWithString:#"YES U PRESSED BUTTON"];
UIButton *refreshbutton=[UIButton buttonWithType:UIButtonTypeCustom];
[refreshbutton setFrame:CGRectMake(15.0f, 330.0f, 150.0f, 32.0f)];
[refreshbutton setCenter:CGPointMake(80.0f,340)];
[refreshbutton setBackgroundImage: normalImage forState: UIControlStateNormal];
[refreshbutton setBackgroundImage: downImage forState: UIControlStateHighlighted];
[refreshbutton setBackgroundImage: selectedImage forState: UIControlStateSelected];
[refreshbutton setTitle:#"Refresh" forState:UIControlStateNormal];
[refreshbutton addTarget:self action:#selector(showMessage:) forControlEvents:UIControlEventTouchUpInside];
}
-(id)showMessage:(id)sender{
// Here I want to get the value of "data" which is defined the method
// "myButtonclick" in the first line. how it is possible..?
}
In the above code in the method "myButtonclick" I set one NSString variable name is "data" I want to get (print there) its value (YES U PRESSED BUTTON) in the method "showMessage" when I press that button.
I know this is done using #selector variable.. but I don't know how it is done
String objects and data objects are two different things. Don't name a string variable “data”—you're setting yourself and anyone else who reads the code up for confusion, and your program up for bugs.
#selector is not a variable. It's part of a literal expression.
Either define your variable that holds the string in showMessage: instead of myButtonClick, or make it an instance variable and create the string and assign it in init.
Making it an instance variable will also fix the leak you have (you alloc the string but never release it), as long as you release the string in dealloc. See the Memory Management entry in Cocoa Core Competencies for more detail on why your current code is wrong.
I'm also confused as to what myButtonClick is supposed to do. It certainly doesn't click the button—all it does is create it (which you'll find much easier to do in IB). Plus, you don't even put the button into a view; you create it and set it up, and then the method ends.