I am currently building an iOS app that uses a UITableView with custom UITableViewCells that include a button. What I want to accomplish is having the UIButton's image change on touch up inside. That part is working fine, the issue is when you scroll, suddenly every few row buttons located in the UITableViewCell have this new image. Below is some of my code. Any help would be appreciated.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *cellID = #"CellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellID];
//YEP BUT
yepBut = [[UIButton alloc] initWithFrame:CGRectMake(23, 16, 64, 32.5)];
[yepBut addTarget:self action:#selector(yepPost:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:yepBut];
}
[yepBut setImage:[UIImage imageNamed:#"yepBtn"] forState:UIControlStateNormal];
return cell;
}
-(void) yepPost:(id) sender{
//UIButton *clicked = (UIButton *) sender;
[sender setImage:[UIImage imageNamed:#"yepBtnACT"] forState:UIControlStateNormal];
}
Thanks!
You need to have a property that you set when the button is touched that you check in cellForRowAtIndexPath. So, in the button's method, have a property, say lastTouched (#property (strong,nonatomic) NSIndexPath *lastTouched, or NSInteger if you don't have sections), that you set to the indexPath (or indexPath.row) of the cell in which the touched button resided (gotten from the cell which you get by searching up through the superviews of the button until you find the cell, or by using tags on your buttons equal to the indexPath.row). After that, you have to reloadData or reloadRowsAtIndexPaths to make the change happen. In cellForRowAtIndexPath, you would set your images like this:
if ([self.lastTouched isEqual:indexPath]) {
[yepBut setImage:[UIImage imageNamed:#"yepBtnACT"] forState:UIControlStateNormal];
else{
[yepBut setImage:[UIImage imageNamed:#"yepBtn"] forState:UIControlStateNormal];
}
When you first initialize lastTouched, you want to set it to an indexPath that doesn't occur in your table, so nothing will have the yepButAct image until you touch one.
Subclassing UITableViewCell fixed my issue.
Related
Sorry for my poor English. I want to add some clickable buttons in my custom tablecell and it works fine but i face a problem when i scroll down the table. After scrolling, the buttons in the upper cell will have no response when i click on it. but when i scroll back to the original position of the cell, the buttons have response again.
I put this
[cell.btn addTarget:self action:#selector(btnPressed:) forControlEvents:UIControlEventTouchUpInside];
in cellForRowAtIndexPath method;
and btnPressed had been triggered before i scroll down. but btnPressed cannot be triggered after i scroll out the bound of the original position of that cell.
Hope someone can understand my problem thanks.
Please tell me how can I make the button response even the table scroll ? thanks in advance
method 1
when i tried the method addtarget in cellForRowAtIndexPath and the coding is like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CustomTableViewCell";
CustomTableViewCell *cell = (CustomTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:cellIdentifier owner:self options:nil];
cell = [nib objectAtIndex:0];
}
[cell.btn addTarget:self action:#selector(btnPressed:)
return cell;
}
method 2
i also tried another method which do not addtarget in cellForRowAtIndexPath
Here's my code with delegate
in customcell.h, i added this
#protocol CustomCellDelegate <NSObject>
- (void)didClickOnbtn;
#end
in customcell.m, i added this
#interface CustomTableViewCell()
#property (nonatomic, strong) NSDictionary *currentDict;
#property (nonatomic,assign) id<CustomCellDelegate>delegate;
#end
and
- (IBAction)didClickOnbtn:(id)sender {
if (self.delegate && [self.delegate respondsToSelector:#selector(didClickOnbtn)]) {
[self.delegate didClickOnbtn];
}
}
and i have set the custom cell delegate to the tableview
in tabelview controller i also have the method didClickOnbtn and <CustomCellDelegate>
and the button can trigger didClickOnbtn method in tableview controller response before scrolling the table.
UPDATE:
I had made a section header view in the table. And I find out when i delete the section header view, the problem is solved. Although I don't know why the section header view would make the button disable, it works well now. Hope this can help someone who are in the same situation as me.
p.s. Both methods work.
I had the same problem. I used the workaround to use Touch Down instead of Touch Up Inside. It's not a perfect solution but a work around as the IBAction for Touch Down as being called even when scrolling is not finished.
How are you setting the frame of button ?
is it in reference to cell frame ? or hardcode like below
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self
action:#selector(aMethod:)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"MyButton" forState:UIControlStateNormal];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0); // example co-ordinate
[cell addSubview :button] ;
In this case , after scrolling down the button co-ordinate will not change and wont work as u said best way to make it right is
create a interface file for UITableViewCell , place a button there and do the wiring
this will solve the scroll problem for you.
also you can try doing this .
button.frame = CGRectMake(cell.frame.origin.x+samplevalue ,cell.frame.origin.y+sampleValue , 160.0, 40.0);
Hope this helps.
Adding target for a button in a tableViewCellForRowAtIndexPath: API is not not recommended way. This API gets called mutiple times when app needs to display the cell.
Move this code to cell class. Create custom class if not done and add this code. In the action method of the button, reference your controller and call some API that does action work for the cell (say open up other controller or so). You can make your view controller as delegate of the cell for this purpose.
Hope this change may solve your problem
I am assuming that you want to add the button to second cell of tableView.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
if (indexPath.row == 0)
{
cell.textLabel.text = #"Cell1";
}
else if (indexPath.row == 1)
{
//Add button to cell and use different reuseIdentifier
UITableViewCell *cell1 = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"btnCell"];
UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(cell1.frame.origin.x+10, cell1.frame.origin.y, self.view.frame.size.width-20, cell1.frame.size.height-10)];
[button setTitle:#"ButtonTitle" forState:UIControlStateNormal];
shareButton.backgroundColor = [UIColor grayColor];
[cell1 addSubview:button];
[shareButton addTarget:self action:#selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside];
return cell1;
}
//Your code...
return cell;
}
I have tried as many as solution I found to resolve this problem.
My latest code is show blow:
static NSString *simpleTableIdentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
[[cell viewWithTag:1000] removeFromSuperview];
[[cell viewWithTag:2000] removeFromSuperview];
// other code for setting view that works perfect then
if(some condition){
UIButton *btn=[UIButton buttonWithType:UIButtonTypeCustom];
[btn setImage:[UIImage imageNamed:#"upgrey.png"] forState:UIControlStateNormal];
btn.frame=upButton.frame;
[btn addTarget:self action:#selector(upButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:btn];
UIButton *btn2=[UIButton buttonWithType:UIButtonTypeCustom];
[btn2 setImage:[UIImage imageNamed:#"downgrey.png"] forState:UIControlStateNormal];
btn2.frame=downButton.frame;
[btn2 addTarget:self action:#selector(downButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:btn2];
btn.tag=1000;
btn2.tag=2000;
}
return cell;
but this does not work. if i add
[[cell viewWithTag:1000] removeFromSuperview];
[[cell viewWithTag:2000] removeFromSuperview];
in start it remove buttons from all cells. and if i do not use this. it shows all cells with these two buttons.
thanks in advance.
Even though you've already accepted your own answer, I'll take a moment to explain why you had that issue and how you can better structure your UITableViews in the future.
The reason why "it shows all cells with these two buttons" when you don't have those removeFromSubview lines is because by implementing dequeueReusableCellWithIdentifier:, you're reusing your cells and the contents thereof. Repeatedly adding the same subview during each call of cellForRowAtIndexPath makes the fact that you're reusing the cells completely pointless since you're reusing absolutely nothing within them. So either, don't reuse your cells at all or, better yet since each cell's contents are exactly the same, do reuse your cells and also reuse those buttons.
There are two good ways to do this. One way to do this is to create a UITableViewCell custom subclass. But since your cells' contents are fairly simple and you say your else conditional's already functional, I'll suggest a different way that might be a bit less code-intensive. Here's my suggestion:
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Create two identifiers -- one for "some condition" one
// for the "other condition"
static NSString *simpleTableIdentifier1 = #"cell1";
static NSString *simpleTableIdentifier2 = #"cell2";
if (some condition) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier1];
// Only add the buttons when cell == nil, i.e. during the first
// time cell's initialized to hold a UITableViewCell
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier1]
UIButton *btn=[UIButton buttonWithType:UIButtonTypeCustom];
[btn setImage:[UIImage imageNamed:#"upgrey.png"] forState:UIControlStateNormal];
btn.frame=upButton.frame;
[btn addTarget:self action:#selector(upButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:btn];
UIButton *btn2=[UIButton buttonWithType:UIButtonTypeCustom];
[btn2 setImage:[UIImage imageNamed:#"downgrey.png"] forState:UIControlStateNormal];
btn2.frame=downButton.frame;
[btn2 addTarget:self action:#selector(downButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:btn2];
}
} else {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier2];
if (cell == nil) {
....
In my UITableView Controller I have cells which each contain a UILabel and UISegmentedController. There are about 20 cells in total. The user can select the UISegmentedControl to control whether object is contained in an array or not.
My problem is that as the user scrolls the UISegmentedControl is duplicated and appears as though there are multiple selections for UISegmentedControl. I have a feeling that this is because cells are being reused but unfortunately I don't have a very good understanding of reusing cells.
I have been trying to play around with:
if(cell == nil){}
but I am not sure what should happen there:
Anyway here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"featuresCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *feature = [features objectAtIndex:indexPath.row];
UILabel *titleLabel = (UILabel *)[cell viewWithTag:9999];
titleLabel.text = feature;
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Yes", #"No", nil]];
segmentedControl.frame = CGRectMake(215, 17, 85, 28);
[segmentedControl addTarget:self action:#selector(valueChanged:) forControlEvents: UIControlEventValueChanged];
segmentedControl.tag = indexPath.row;
[cell addSubview:segmentedControl];
if ([selectedFeatures containsObject:feature]) {
segmentedControl.selectedSegmentIndex = 0;
}
else{
segmentedControl.selectedSegmentIndex = 1;
}
return cell;
}
Your help will be greatly appreciated,
Thanks for your time.
cell is never nil so that's not going to help you.
The trick is to mark the cell in some way so as to know whether the segmented control has already been added. The best way is to use the segmented control's tag. Unfortunately you seem to be using the tag for some other purpose, so you'll need to stop doing that:
if (![cell viewWithTag:12345]) {
UISegmentedControl *segmentedControl = // ....;
// ....
segmentedControl.tag = 12345;
[cell.contentView addSubview:segmentedControl];
}
And now, you see, you are guaranteed that there is exactly one segmented control in the cell, and you can find it by calling [cell viewWithTag:12345].
The general rule is that each cell subclass should implement - (void)prepareForReuse to make it ready for configuration in - (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath. I think you can solve your problem by subclassing UITableViewCell, creating each cell with the required UISegmentedControl, and providing a method to configure the segmented control as required.
prepareForReuse should reset the segmented control so that each time you configure your custom cell it is in a known, empty state.
The advantage of this approach is that you are not only reusing the table view cell, but also the segmented control and any other decoration you want the cell to contain. Additionally you have moved the configuration of the cell into the cell object.
Briefly, in the cell subclass init method you create the segmented control and add it as a subview of the contentView.
In the cell subclass configuration method you provide you set the segmented control's segments etc.
In the cell subclass prepareForReuse you remove all those segments and leave the segmented control in a state ready for configuration next time you reuse that cell.
To go even further, start using auto layout to position the segmented control in the cell rather than using a set of hard numbers in a frame. If you get those working on one kind of device that's great but as soon as your cells have different dimensions (in a table view on a different device) things will not look so good.
Try this,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"featuresCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSString *feature = [features objectAtIndex:indexPath.row];
UILabel *titleLabel = (UILabel *)[cell viewWithTag:9999];
titleLabel.text = feature;
UISegmentedControl *segmentedControl;
if ([cell.contentView viewWithTag:kSegmentTag]) { //kSegmentTag a tag
segmentedControl = (UISegmentedControl *)[cell viewWithTag:kSegmentTag];
} else {
segmentedControl = [[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Yes", #"No", nil]];
segmentedControl.frame = CGRectMake(215, 17, 85, 28);
[segmentedControl addTarget:self action:#selector(valueChanged:) forControlEvents: UIControlEventValueChanged];
segmentedControl.tag = kSegmentTag;
[cell.contentView addSubview:segmentedControl];
}
if ([selectedFeatures containsObject:feature]) {
segmentedControl.selectedSegmentIndex = 0;
}
else{
segmentedControl.selectedSegmentIndex = 1;
}
return cell;
}
and in valueChanged:
- (IBAction)valueChanged:(id)sender {
UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
CGPoint tablePoint = [segmentedControl convertPoint:segmentedControl.bounds.origin toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:tablePoint];
//here indexpath.row give changed row index
}
I am wondering whether someone has had similar problem that I am experimented lately.
Let me describe you a little bit further about my issue. I have got a UITableViewController where I have designed a set of custom UITableViewCell in the IB. Each UITableViewCell has got different elements like a UIButton, UITextField and UILabel, etc. Obviously, each UITableViewCell has a different identifier.
Once defined all my UITableViewCells, next step is to instantiate the cell on tableView:cellForRowAtIndexPath:. What I do in this method is depending on section and row, I instantiate the different UITableViewCells by dequeueReusableCellWithIdentifier:forIndexPath:.
Everything seems to work perfectly and correctly created on the table, but the problem arrives when I scroll down. At the very top, I have got a UITableViewCell with a UIButton that I have specified with a concrete action to perform when it is clicked. Scrolling down, there are a couple of UITableViewCells with the same format (an UIButton inside with different actions specified).
The problem is, when a click the first button from the bottom side, this button performs the first action that I have defined on the very top UIButton and its own action.
It seems that when uitableviewdelegate creates new cells or reuse them, it nests functionalities from other indexPath instead of the specified indexPath....
Hope that I have explained myself properly.
Thank you in advance.
[EDIT]
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [[UITableViewCell alloc] init];
NSLog(#"indexpath value is: %#", indexPath);
if (indexPath.section == 0)
cell = [self buildSection_0:tableView getIndexPath:indexPath];
else if (indexPath.section == 1)
cell = [self buildSection_1:tableView getIndexPath:indexPath];
else if (indexPath.section == 2)
cell = [self buildSection_2:tableView getIndexPath:indexPath];
else if (indexPath.section == 3)
cell = [self buildSection_3:tableView getIndexPath:indexPath];
else if (indexPath.section == 4)
cell = [self buildSection_4:tableView getIndexPath:indexPath];
return cell;
}
-(UITableViewCell *)buildSection_0:(UITableView *)tableView getIndexPath:(NSIndexPath *)indexPath{
NSString *identifier;
UITableViewCell *cell;
if (indexPath.row == 0){
identifier = #"headerCell";
cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
}
else if (indexPath.row == 1){
identifier = #"buttonCell";
cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
UIButton *button = (UIButton *)[cell viewWithTag:3001];
[button setTitle:#"Scan" forState:UIControlStateNormal];
[button addTarget:self action:#selector(scan) forControlEvents:UIControlEventTouchUpInside];
}
return cell;
}
-(UITableViewCell *)buildSection_3:(UITableView *)tableView getIndexPath:(NSIndexPath *)indexPath{
NSString *identifier;
UITableViewCell *cell;
if (indexPath.row == [[job jobLocations] count]) { // Adding button insert more Locations
identifier = #"buttonCell";
cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
UIButton *button = (UIButton *)[cell viewWithTag:3001];
[button setTitle:#"Add Location" forState:UIControlStateNormal];
[button addTarget:self action:#selector(newLocation) forControlEvents:UIControlEventTouchUpInside];
}
else{ // Showing different Locations
identifier = #"locationCell";
cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
UILabel *label = (UILabel *)[cell viewWithTag:3007];
UITextField *textField = (UITextField *)[cell viewWithTag:3008];
[label setText:[NSString stringWithFormat:#"Location #%ld", (long)indexPath.row + 1]];
[textField setText:[[[job jobLocations] objectAtIndex:indexPath.row] locationType]];
}
return cell;
}
dequeueReusableCellWithIdentifier:forIndexPath will reuse cells that are no longer visible, you may want to implement prepareForReuse on your cell, or reset it after you dequeue it.
[EDIT]
Seeing you added your code, you should remove previous target/actions from the button before adding the new ones, as the old one will still be there if a cell is being reused:
[button removeTarget:self action:NULL forControlEvents:UIControlEventTouchUpInside];
[button addTarget:...
Add a tag to the button that corresponds to the indexPath.row. Then in your button action method, you can determine which button was clicked by looking at the (UIButton *)sender's tag.
I am showing a image in CellImage when any cell is clicked it works fine but problem is that when a new cell is selected then previous cell image is also visible i want that when user selects on new cell previous cellImage should be hidden.
here is the code
-(IBAction)imageButtonAction:(id)sender{
CustomCell *cell = (CustomCell *) [[sender superview] superview];
UIButton *btn = (UIButton *)sender;
UIView *backimage = [[UIView alloc] initWithFrame:CGRectMake(300,0,312,105)];
backimage.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"popupj.png"]];
[backimage setUserInteractionEnabled:YES];
[cell addSubview:backimage];
}
I am calling this method on the button which is in cell i have used custom cell
You might try this, it may help you.
-(IBAction)imageButtonAction:(id)sender{
CustomCell *cell = (CustomCell *) [[sender superview] superview];
UIButton *btn = (UIButton *)sender;
for (UIView *subView in cell.contentView.subviews) {
if([subView isKindOfClass:[UIView class]]){
subView.hidden=YES;
}
}
UIView *backimage = [[UIView alloc] initWithFrame:CGRectMake(300,0,312,105)];
backimage.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"popupj.png"]];
[backimage setUserInteractionEnabled:YES];
[cell addSubview:backimage];
}
Or you can even use the block, to loop through subviews and hide them
Cheers
There is a value on a table view xib called selection, check that its value is set to single selection in your xib. Or programmatically set this property to NO:
#property(nonatomic) BOOL allowsMultipleSelection
Change the hidden value property of your previous selected cell in tableView:didSelectRowAtIndexPath:
Access the cell by using:
UITableViewCell *cell = (UITableViewCell *)[tableView cellForRowAtIndexPath:previousIndex];
cell.yourImage.hidden = YES;
In your button action you could set maintain the previousIndexPath which should be declared in the .h file . By using this previousIndexPath you can easily hide the previously loaded image:
CustomCell *cell=(CustomCell *)[yourTable cellForRowAtIndexPath:previousIndexPath];
cell.yourImage.hidden=TRUE;