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.
Related
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
I am trying to create a UITableView with a custom UIButton in each table cell. I implemented like this..
#implementation CouponDetailsCustomTableViewCell
...............
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
[self setBackgroundColor:[UIColor whiteColor]];
CGRect frame = self.contentView.frame;
self.radioButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.radioButton setImage:[UIImage imageNamed:#"radio_blank.png"] forState:UIControlStateNormal];
[self.radioButton setImage:[UIImage imageNamed:#"radio_selected"] forState:UIControlStateSelected];
[self.radioButton setFrame:CGRectMake(16, 10, 29, 29)];
[self.radioButton addTarget:nil action:#selector(radioButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:radioButton];
}
#end
and UITableView Delegate as......
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *COUPON_CELL_ID = #"CouponCell" ;
CouponDetailsCustomTableViewCell * couponCell = (CouponDetailsCustomTableViewCell *) [tableView dequeueReusableCellWithIdentifier:COUPON_CELL_ID];
if (couponCell == nil) {
couponCell = [[[CouponDetailsCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:COUPON_CELL_ID] autorelease];
couponCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
[couponCell.radioButton setSelected:NO];
return couponCell;
}
and the radioButtonPressed method is
-(void)radioButtonPressed:(id) sender
{
[sender setSelected:YES];
}
Now i run the program and a custom UIButton is shown in every table row . If i click on a button it gets selected (shows the radio_selected.png).
The problem arises when i scroll down the table (i am showing only 4 rows of the table in the window) . When i scroll up again..what i see is the clicked button is showing the radio_blank.png.
I am new in iPhone development. i dont know why is this happening. The most i can guess is the button property is changing .. setSelected:NO.
Someone please give me suggestions to fix this problem.
Thank you.
When you scroll your UITableView, hidden cells are not rendered anymore and might be reused for cells that are becoming visible. If a new cell becomes visible, tableView:cellForRowAtIndexPath: gets called.
The problem is that you're setting the selected state there:
[couponCell.radioButton setSelected:NO];
Therefore, whenever you scroll your cell out of the visible area and back again, it gets reset to selected = NO.
I suggest you create a NSMutableDictionary where you store the selection state of each row/NSIndexPath, which you then re-apply in tableView:cellForRowAtIndexPath:.
replace [couponCell.radioButton setSelected:NO]; in tableView:cellForRowAtIndexPath: with code that sets the selected property depending on a state from your dataSource.
something along those lines:
/* get the button state from your data source */
FancyCouponObject *coupon = [self.coupons objectAtIndex:indexPath.row];
BOOL buttonState = coupon.buttonState;
[couponCell.radioButton setSelected:buttonState];
The cells of a tableView are reused when they are moved off screen. You can't save state in them.
problem is when you scroll the table at that time your cellForRowAtIndexPath: delegate method called for every row... so here when its called at time your setSelected Method call with NO argument like bellow...
[couponCell.radioButton setSelected:NO];
so when you scroll table at time your setSelected method call and your button turn with radio_blank.png
...
:)
-(void)radioButtonPressed:(id) sender
{
[sender setSelected:YES];
}
in this method you are setting button as selected and for selected button you have set the radio_blank.png image
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.....
I want to get multiple custom texts on cell of UITableView. I want these texts after clicking on a dynamically added button on the cell. I can't use didRowSelectedAtIndex method. I have my own selector which I am executing but I don't know how can I get texts on each cell. Anybody can help me?
Thank
You add a UILabel to the cell's view. Just like you did it with your custom Button.
You can add a UILabel to all cells in advance. If the UIButton on a cell is cklicked, you have to save that state information somewhere (NSArray). When it comes to rendering the cell, you ask the array if the cell at current index should show some text and set it up if so...
If you want immediate rendering call [self.tableView reloadData]
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (array at index indicates clicked) {
myCustomCell.label1 = #"foo";
} else {
myCustomCell.label1 = #"bar";
}
}
If you don't like customs cells. You can apply a tag to the label, add the label to the cell and get it from cell by querying it afterwards by that tag.
Make your labels as global variables
Create yur cell as default style cell.
Add your labels to contentView of cell without text.
Add your button.
In the method that button invokes after click set texts to your labels.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
customLabel1 = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, 50, 20)];
customLabel2 = [[UILabel alloc] initWithFrame:CGRectMake(55, 5, 50, 20)];
[cell.contentView addSubview:customLabel1];
[cell.contentView addSubview:customLabel2];
}
and
- (IBAction) click
{
customLabel1.text = #"YAYA";
customLabel2.text = #"GAGA";
}
You could maybe try creating a custom button class by extending UIButton and add properties to contain references to the NSString objects (or index of the original array where the strings are stored etc).
Then on button click you can cast the sender to your custom button type and simply retrieve the property values.
I have created three buttons and set the background image for the normal state and the selected state. When i click the button, changed one image as the selected state. But when is scroll the table view, the selected image is not retained (previous selected cell) which means it comes for the normal state.
My code is,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
likeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *likeSelectedImage = [UIImage imageNamed:#"like.png"];
//set image as background for button in the normal state
[likeBtn setBackgroundImage:likeSelectedImage forState:UIControlStateNormal];
[likeBtn addTarget:self action:#selector(likeAction:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:likeBtn];
}
My Button action is,
-(void) likeAction : (id) sender
{
UIImage *likeSelectedImg = [UIImage imageNamed:#"like-selected.png"];
UIImage *likeImg = [UIImage imageNamed:#"like.png"];
if ([sender isSelected]) {
[sender setImage:likeImg forState:UIControlStateNormal];
[sender setSelected:NO];
}else {
[sender setImage:likeSelectedImg forState:UIControlStateSelected];
[sender setSelected:YES];
}
}
SO my problem is, when i scroll the table view cell, the previously selected state of an image is not retained. Because the cellForRowAtIndex method has repeatedly called, when i scrolled the table. so it automatically sets "[likeBtn setBackgroundImage:likeSelectedImage forState:UIControlStateNormal];". How to avoid this problem?
So please help me out?
Thanks!
You are seeing this due to the design pattern that UITableView's use for displaying data. To minimize resource usage only the table cells that are currently on screen are guaranteed to be kept in memory. As cells are scrolled off-screen the cell objects are intended to be recycled to use for the new cell's appearing at the other end of the list and therefore lose their state.
I'm assuming you've trimmed your code somewhat as you're returning a cell in tableView: cellForRowAtIndexPath: but there's no reference to that variable anywhere else in the snippet you posted. This makes it impossible to see how you're getting the cell before adding the button.
Bit of a stab in the dark as to how your code works but here's a high level overview of what you need to do to retain state.
Create some controller-level storage such as an NSArray to hold all your button states for all the table rows.
In the likeAction determine which row the button is from (possibly assign the row number to the button's tag property when creating it) and update the state for the corresponding row in your NSArray
In tableView: cellForRowAtIndexPath: set up your button with the correct image by using the state which is fetched from the NSArray for the given indexPath.
the above one is not correct in syntax:
cell = [[[ToDoSummaryTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
give each cell a unique identifier, it should work.
See any sample code which uses 'reuseidentifier' in XCode IOS api.
Thanks,
Bharath
I re-edit your above code. I hope, I will help you.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
likeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *likeSelectedImage = [UIImage imageNamed:#"like.png"];
//set image as background for button in the normal state
[likeBtn setBackgroundImage:likeSelectedImage forState:UIControlStateNormal];
[likeBtn setBackgroundImage:likeSelectedImage forState:UIControlStateHighlighted];
[likeBtn addTarget:self action:#selector(likeAction:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:likeBtn];
}
You can use 'reuseIdentifier' for each cell with an identifier.
cell = [[[ToDoSummaryTableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:.<identifier>] autorelease];