Removing target-actions for UIButton - iphone

I have a UIButton that is added to each UITableViewCell (except for 2 cells) in a tableview.
The button's target is the UITableViewController.
I noticed that the app has crashed when the action has been sent to the wrong target. I'm assuming that this is because the target has somehow been deallocated (even though, if the UITableViewController has been deallocated, the buttons should not be visible, and not pressable (and should be deallocated themselves)).
I'm guessing I need to balance the addTarget method, with the removeTarget. Like KVO and retain/release.
But I'm not sure where to do this, because I only have a reference to the button when it is being created and added to the cells, in cellForRowAtIndexPath:?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
UIButton *extraButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[extraButton setFrame:CGRectMake(0, 0, 60, 30)];
[extraButton setTitle:#"Meta" forState:UIControlStateNormal];
[extraButton addTarget:self action:#selector(extraButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
cell.accessoryView = extraButton;
}
if (indexPath.row == kNoExtraButtonRow) {
cell.accessoryView.hidden = YES;
} else {
cell.accessoryView.hidden = NO;
}
//set textlabels etc...
return cell;
}

It is very unusual to need to do this. What is more likely is that you're incorrectly managing memory some other way. In particular, the target of a UIButton should generally be the UIViewController that owns that button. In most good designs, the button always has a shorter lifespan than the controller. Are you retaining the UIButton elsewhere? Are you using a nib file to manage your button, or generating it programmatically? It is somewhat common for people to accidentally create multiple instances of UI elements when they create them programmatically (one of several reasons that nib files are preferred).
Are you sure to use accessors for all your ivars (particularly the button in this case)? Direct access to ivars is the most common way developers create duplicate UI elements. Always use accessors (except in init and dealloc).

I think it will help
[someControl removeTarget:nil
action:NULL
forControlEvents:UIControlEventAllEvents];

If you have subclassed UIButton for this or have subclassed UITableViewCell then you can put the code in there to remove the target action for the button when the cell or button is deallocated. In the UITableViewCell's dealloc you can call removeTarget on the its button, or in the button's dealloc it could call removeTarget for itself.

You do not need to removeTarget and please make sure that you are setting Target as self. Your code works fine here for me.

It's assumed that the table view cells enter the reuse queue and come back off of it untouched, but as a sanity check, maybe what you want to do is in the case where ( cell != nil ) coming off the dequeue, you want to removeTarget and re-establish the target anyway.
Basically do your own inline -prepareForReuse in the ( cell != nil ) case and set the whole thing up (except for the actual table cell alloc) in every case.

[extraButton addTarget:nil action:#selector(extraButtonPressed:) forControlEvents:UIControlEventTouchUpInside];

Try This.
if (cell == nil){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]retain];
UIButton *extraButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect]retain];
[extraButton setFrame:CGRectMake(0, 0, 60, 30)];
[extraButton setTitle:#"Meta" forState:UIControlStateNormal];
[extraButton addTarget:self action:#selector(extraButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
cell.accessoryView = extraButton;
}
remove autorelease and add retain in Tableviewcell allocation and in buton allocation.....

Related

iphone development: changing the button image on tableviewcell

In my app I am facing with a really weird problem. I am trying to solve it for hours but could not find what is the problem so far.
Well, I have a tableview with custom cells. and each cell I have a button. It is is like an empty box. When the button pressed, I want to change the related cell image of that button. Actually I successfully do that but somehow, some other cell's button images are also changing and I could not figure out why. Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
//create new cell
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[cell.tickButton setTag:indexPath.row];
[cell.tickButton addTarget:self action:#selector(buttonWasPressed:)forControlEvents:UIControlEventTouchUpInside];
//cell has 2 label so I filled them with the contents of my object array that part is working with no problem
cell.leftLabel.text = [NSString stringWithFormat:#"%#", [[contacts objectAtIndex:indexPath.row]name]];
cell.rightLabel.text = [NSString stringWithFormat:#"%#", [[contacts objectAtIndex:indexPath.row]email]];
return cell;
}
in my buttonWasPressed method I simply do:
-(IBAction)buttonWasPressed:(id)sender
{
UIButton *button = (UIButton *)sender;
[button setImage:[UIImage imageNamed:#"ticck.jpeg"] forState:UIControlStateNormal];
}
As I said before it works but also changes some other buttons images and also when I scroll down and get back to the initial position I see that some button images are changed.
You can access that cell and can change the button image as :
-(IBAction)buttonWasPressed:(id)sender
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:your_row inSection:your_section];
UITableViewCell *currentCell = [table cellForRowAtIndexPath:indexPath];
UIButton *button = (UIButton *)[currentCell viewWithTag:(UIButton *)sender.tag];
[button setImage:[UIImage imageNamed:#"ticck.jpeg"] forState:UIControlStateNormal];
}
Hope it helps you.
You can deal with touch within CustomCell class. Just move method -(IBAction)buttonWasPressed:(id)sender within your custom class and add
tickButton addTarget:selfaction:#selector(buttonWasPressed:)forControlEvents:UIControlEventTouchUpInside in init method.
The problem is calling
[cell.tickButton addTarget:self action:#selector(buttonWasPressed:)forControlEvents:UIControlEventTouchUpInside];
method.
You add target everytime when your cells are reused and remove never. So your buttons have many targets and action method get called multiple times respectively. It may cause a memory leak.
Just put the addTarget method in if clause.
It will solve your one problem, which is your method get called multiple times. But when the cell is reused the button has the changed image. You need to set it back.
The best way to do that, you add a property to the contact object which are preseted in cells and set a value when the action method is fired. Then in cellForRowAtIndexPath: method you can set the image of the button by checking that property.
Do this
NSString *CellIdentifier = [NSString stringWithFormat:#"cell%d",indexPath.row];
While you set the default image to button also do this
[button setImage:[UIImage imageNamed:#"ticck.jpeg"] forState:UIControlStateSelected];
In your button action method do this:
-(IBAction)buttonWasPressed:(id)sender
{
UIButton *button = (UIButton *)sender;
if(button.selected==NO) {
[button setSelected:YES];
}
}
Hope this helps.
To reuse your cell, you have to make it clean after dequeueing. In this case, you should reset your image first:
[cell.tickButton setImage:nil forState:UIControlStateNormal];
Before assigning anything to your cell.
You must save button's tag which was pressed than try this
if(pressed[cell.tickButton.tag])
[button setImage:[UIImage imageNamed:#"ticck.jpeg"] forState:UIControlStateNormal];
else
[cell.tickButton setImage:nil forState:UIControlStateNormal];
I guess you are giving the default image to your button inside the CustomCell class. This problem is very common in UITableviews.
All you need to do is keep track of the cells for which the button has been pressed. Do all this inside the cellForRowAtIndexPath method.
For example:
update the code inside cellForRowAtIndexPath method
if (cell == nil)
{
//create new cell
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if(yourCondition==YES){
change the button image to the special image.
}
else{
keep the button image as default image.
}
return cell;
To keep track of the cells you can store the index path of the cells(whose button has been tapped) in an array. Inside cellForRowAtIndexPath check if the current indexPath is present inside your array or not

Removing image from parent view

In my iPhone app I have a table view where I add a tick image to a cell if that objects 'isConfirmed' value is true. When entering the detailed view I can edit the confirmed value, and upon popping back to the main table view I need to see the update and not only when I view the main table from a fresh.
So I am using this code in my tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method`:
UIImageView *tickImg = nil;
//If confirmed add tick to visually display this to the user
if ([foodInfo.isConfirmed boolValue])
{
tickImg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ConfirmedTick.png"]];
[tickImg setFrame:CGRectMake(0, 0, 32, 44)];
[cell addSubview:tickImg];
}
else
{
[tickImg removeFromSuperview];
}
What this does it successfully add the tick image to my cells which have a true value for isConfirmed and when going into the detail view of an object and setting it to TRUE and retuning, the tick appears, however I can't get it to work the other, so if the tick is there and I go into the detail view to unconfirmed it, the tick doesn't disappear.
This is the code that is executed if [foodInfo.isConfirmed boolValue] is false:
UIImageView *tickImg = nil;
[tickImg removeFromSuperview];
Clearly this will not work -- tickImg is not pointing to the UIImageView. You need to somehow save the reference to the UIImageView. You could add the tickImg variable to the header of your class or make it a property or something.
Are you calling [self.tableView reloadData]; on the VC's viewWillAppear:?
Also, the approach you're using to configure the cell is error-prone. Since the tableView is reusing the cells, you can't be sure what state a cell is in when you dequeue it.
A better approach would be to build the cells consistently:
static NSString *CellIdentifier = #"MyCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
// always create a tick mark
UIImageView *tickImg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ConfirmedTick.png"]];
tickImg.tag = kTICK_IMAGE_TAG;
tickImg.frame = CGRectMake(0, 0, 32, 44);
[cell addSubview:tickImg];
}
// always find it
UIImageView *tickImg = (UIImageView *)[cell viewWithTag:kTICK_IMAGE_TAG];
// always show or hide it based on your model
tickImg.alpha = ([foodInfo.isConfirmed boolValue])? 1.0 : 0.0;
// now your cell is in a consistent state, fully initialized no matter what cell
// state you started with and what bool state you have

UITableViewCell from contentView subview

I have created the cells with labels and using checkaMarksAccessory. The few last cells have UITextFields which can user modifi, and those have selector on UIControlEventEditingDidEnd where i want change the state of the cell to checked.
How can i get the cell in the selector? Doesn't have the object some parentView?
The way i inserting the object to cell.
UITextField *textfield = [[UITextField alloc] initWithFrame:CGRectMake(10, 25, 200, 30)];
[textfield setBorderStyle:UITextBorderStyleRoundedRect];
[textfield addTarget:self action:#selector(vybavaDidFinishEdit:) forControlEvents:UIControlEventEditingDidEnd];
[cell.contentView addSubview:textfield];
I'm not sure if it's safe to assume cell.contentView.superview == cell. Might Apple change this? I doubt it. But, I don't see anywhere in the documentation that says a cell's content view is a direct subview of the cell.
If you've added a UIGestureRecognizer to one of your subviews of the cell's content view, then you can get a reference to the cell with:
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:[gestureRecognizer locationInView:self.tableView]];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
Table View Animations and Gestures sample code uses indexPathForRowAtPoint: this way.
If you must traverse superviews, I think using a function like the one below is a bit safer.
UITableViewCell *ACMContentViewGetCell(UIView *view)
{
while ((view = view.superview)) {
if ([view isKindOfClass:[UITableViewCell class]]) {
return (UITableViewCell *)view;
}
}
return nil;
}
But that function still assumes contentView is within its cell, which I also didn't see anywhere in the documentation.
So perhaps, the best solution is to rearchitect your code so that you don't need to get cell from contentView, or if you must, then add an instance variable from the subview of contentView to cell.
ok so the way is to use superview. The superview is component which own the object. If i want get the UITableViewCell from UITextField i used [[UITextField superview] superview].

Strange bug in tableView

I have a UITableView, I can add and delete cell to this table. I also have two buttons. 1 button adds "1" to the cell's text, Which is 1 so basically it counts when pressing the + button and subs when pressing the - button. My problem is with the very last cell. If i add 5 cells, its the 5th cell that has the problem. If i add 200 cells its the 200th cell, etc. The problem is when i press the - button, all the other cells keep turning blue when pressed, and this button stops turning blue. It stays white when i press it when the cells text is 0. I want it to keep turning blue like all the other cells when pressed. Here is my code:
- (IBAction)subtractLabelText:(id)sender
{
cell = (UITableViewCell*)[sender superview];
if ( [[cell.textLabel text] intValue] == 0){
[newBtn setEnabled:NO];
}
else{
cell.textLabel.text = [NSString stringWithFormat:#"%d",[cell.textLabel.text
intValue] -1];
[newBtn setEnabled:YES];
}
}
This method is hooked up to the sub "-" button. Also, when i press the button when the text is = 0, the button is there but when i press it, it selects the cell and the table cell turns blue as if i selected that! Please help! Thanks everybody!
cellForRow:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
cell.imageView.image = [imageArray objectAtIndex:indexPath.row];
cell.textLabel.text = [cells objectAtIndex:indexPath.row];
newBtn = [[UIButton alloc]init];
newBtn=[UIButton buttonWithType:UIButtonTypeRoundedRect];
[newBtn setFrame:CGRectMake(260,20,55,35)];
[newBtn addTarget:self action:#selector(subtractLabelText:)
forControlEvents:UIControlEventTouchUpInside];
[newBtn setTitle:#"-" forState:UIControlStateNormal];
[newBtn setEnabled:YES];
[cell addSubview:newBtn];
subBtn = [[UIButton alloc]init];
subBtn=[UIButton buttonWithType:UIButtonTypeRoundedRect];
[subBtn setFrame:CGRectMake(200,20,55,35)];
[subBtn addTarget:self action:#selector(addLabelText:)
forControlEvents:UIControlEventTouchUpInside];
[subBtn setTitle:#"+" forState:UIControlStateNormal];
[subBtn setEnabled:YES];
[cell addSubview:subBtn];
return cell;
}
Based on your cellForRowAtIndexPath method, you are creating and adding new buttons to each cell regardless of whether they are being reused or 'brand new'.
Your code is doing something like this:
Check if there is a cell to be dequeued/reused
If yes, grab a reference to it. If no, create a new one.
Add buttons XYZ to the cell (your mistake is here)
Return the cell for the tableview to use
Imagine that in step 2, if you a reusing an existing cell, this cell already has the buttons XYZ added to it so you are essentially adding two extra button to it when you didn't need to.
You should move the code that sets up the cell buttons to inside the if (cell == nil) loop so the buttons are only created and added to the content view if you are initialising a new cell object.
Also make sure you look at the additional comments below which are not directly related to your question but may be relevant nonetheless.
==================================================================
[EDIT - ANSWER ABOVE, BUT THIS IS ALSO RELEVANT SO I LEFT IT HERE]
This will not solve your problem, but you should be adding your buttons to the cell's contentview instead of directly to the cell, i.e.:
[cell.contentView addSubView:buttonABC];
Once that's done, you will need to call superview twice to get the cell reference in your subtract/add method:
cell = (UITableViewCell*)[[sender superview] superview];
From Apple's documentation on UITableViewCell
The content view of a UITableViewCell object is the default superview
for content displayed by the cell. If you want to customize cells by
simply adding additional views, you should add them to the content
view so they will be positioned appropriately as the cell transitions
into and out of editing mode.
At a guess, I'd say it has to do with how the UITableViewCells are being reused, in that the UI doesn't 'know' about the subview buttons on those cells.
Try commenting out the code in -cellForRowAtIndexPath that dequeues the reusable cells (so in effect you're always creating a new UITableViewCell regardless of whether cell==nil or not).
Edit:
You're referencing newBtn in -subtractLabelText, but this is an ivar that doesn't necessarily refer to the button that sent the message. Try [sender setEnabled:NO] instead.

UIImageView Animation Stopping in UITableView

I have a UIImageView inside of a UITableViewCell. The UIImageView animates. For some odd reason, when the cell goes out of view, the animation stops. The UIImageView is never initialized again and the UIImageView is never explicitly told to - (void)stopAnimating; so I'm not sure why it's stopping.
Here's the interesting parts of my cellforRowAtIndexPath:. As you can
cell = (BOAudioCell *)[tableView dequeueReusableCellWithIdentifier:AudioCellIdentifier];
if (!cell) {
cell = [[[BOAudioCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:AudioCellIdentifier] autorelease];
BOPlayControl *playControl = [(BOAudioCell *)cell playControl];
[[playControl playbackButton] addTarget:self action:#selector(playbackButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
}
int index = [indexPath row];
BOModel *model = [models objectAtIndex:index];
[[(BOAudioCell *)cell titleLabel] setText:[model title]];
[[(BOAudioCell *)cell favoriteButton] addTarget:self action:#selector(favoriteButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
As you can tell, I'm not setting the appropriate animation begin point for the cell because there is not a built in way with a UIImageView.
You should have exposed the UIImageView on your cell and started the animation in the cellForRowAtIndexPath method or do an override of the prepareForReuse method of the UITableViewCell and started the animation in that method.
prepareForReuse is called every time your reusable cell is dequeued from the reusable queue. Just to note, if you were to use this, do not forget to invoke the superclass implementation.
When the cell goes off-screen, it will be released, and the subview (the image view) will be released as well, causing the animation to be stopped.
Keep a -retain-ed instance of that image view somewhere to avoid this.
I wrote a workaround. I'm just using a NSTimer to coordinate manually animating the UIImageView. It works great.