how do i know what Cell's button in UItableview clicked - ios

I have a UITableView with custom Cell (subclass of UITableViewCell). Each cell view has two buttons btn1 and btn2. I set my ViewController to handle the TouchUp Inside of those btn1s.
How do I know in which cell button clicked?

I'd put a tag property on it. Say the left one has a tag of 1, and the right has a tag of 2.
All UIViews have a tag property, which is just an integer value.
#property (nonatomic) NSInteger tag;
Then when you have received a button tap, you can ask what the button's tag is - 1 or 2, and you'll know which button it was.
If you want to know which index path that button belonged to, you can grab the button's superview, which should be the cell, or the button's superview's superview, if you placed it on the cell's content view (as you generally should):
UITableViewCell *cell = (UITableViewCell *)[button superview];
NSIndexPath *pathForSelectedButton = [tableView indexPathForCell:cell];
Note that the above code assumes the button was placed directly on the cell.

UIButton has a tag property. You can set a unique value to each of your button, such as indexPath.row*2 and indexPath.row*2 + 1 if there is only one section.
And then you can add target method for each button.
[button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside]
Lastly, implement buttonPressed method like
- (void)buttonPressed:(UIButton *)sender {
NSInteger tag = sender.tag;
// your code here.
}

Related

Correct way to setting a tag to all cells in TableView

I'm using a button inside a tableView in which I get the indexPath.row when is pressed. But it only works fine when the cells can be displayed in the screen without scroll.
Once the tableView can be scrolleable and I scrolls throught the tableview, the indexPath.row returned is a wrong value, I noticed that initially setting 20 objects, for example Check is just printed 9 times no 20.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
lBtnWithAction = [[UIButton alloc] initWithFrame:CGRectMake(liLight1Xcord + 23, 10, liLight1Width + 5, liLight1Height + 25)];
lBtnWithAction.tag = ROW_BUTTON_ACTION;
lBtnWithAction.titleLabel.font = luiFontCheckmark;
lBtnWithAction.tintColor = [UIColor blackColor];
lBtnWithAction.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[cell.contentView addSubview:lBtnWithAction];
}
else
{
lBtnWithAction = (UIButton *)[cell.contentView viewWithTag:ROW_BUTTON_ACTION];
}
//Set the tag
lBtnWithAction.tag = indexPath.row;
//Add the click event to the button inside a row
[lBtnWithAction addTarget:self action:#selector(rowButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
//This is printed just 9 times (the the number of cells that are initially displayed in the screen with no scroll), when scrolling the other ones are printed
NSLog(#"Check: %li", (long)indexPath.row);
return cell;
}
To do something with the clicked index:
-(void)rowButtonClicked:(UIButton*)sender
{
NSLog(#"Pressed: %li", (long)sender.tag);
}
Constants.h
#define ROW_BUTTON_ACTION 9
What is the correct way to get the indexPath.row inside rowButtonClicked or setting a tag when I have a lot of of cells in my tableView?
My solution to this kind of problem is not to use a tag in this way at all. It's a complete misuse of tags (in my opinion), and is likely to cause trouble down the road (as you've discovered), because cells are reused.
Typically, the problem being solved is this: A piece of interface in a cell is interacted with by the user (e.g. a button is tapped), and now we want to know what row that cell currently corresponds to so that we can respond with respect to the corresponding data model.
The way I solve this in my apps is, when the button is tapped or whatever and I receive a control event or delegate event from it, to walk up the view hierarchy from that piece of the interface (the button or whatever) until I come to the cell, and then call the table view's indexPath(for:), which takes a cell and returns the corresponding index path. The control event or delegate event always includes the interface object as a parameter, so it is easy to get from that to the cell and from there to the row.
Thus, for example:
UIView* v = // sender, the interface object
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
UITableViewCell* cell = (UITableViewCell*)v;
NSIndexPath* ip = [self.tableView indexPathForCell:cell];
// and now we know the row (ip.row)
[NOTE A possible alternative would be to use a custom cell subclass in which you have a special property where you store the row in cellForRowAt. But this seems to me completely unnecessary, seeing as indexPath(for:) gives you exactly that same information! On the other hand, there is no indexPath(for:) for a header/footer, so in that case I do use a custom subclass that stores the section number, as in this example (see the implementation of viewForHeaderInSection).]
I agree with #matt that this is not a good use of tags, but disagree with him slightly about the solution. Instead of walking up the button's superviews until you find a cell, I prefer to get the button's origin, convert it to table view coordinates, and then ask the table view for the indexPath of the cell that contains those coordinates.
I wish Apple would add a function indexPathForView(_:) to UITableView. It's a common need, and easy to implement. To that end, here is a simple extension to UITableView that lets you ask a table view for the indexPath of any view that lies inside one of the tableView's cells.
Below is the key code for the extension, in both Objective-C and Swift. There is a working project on GitHub called TableViewExtension-Obj-C that illustrates the uses of the table view extension below.
EDIT
In Objective-C:
Header file UITableView_indexPathForView.h:
#import <UIKit/UIKit.h>
#interface UIView (indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view;
#end
UITableView_indexPathForView.m file:
#import "UITableView_indexPathForView.h"
#implementation UITableView (UITableView_indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view {
CGPoint origin = view.bounds.origin;
CGPoint viewOrigin = [self convertPoint: origin fromView: view];
return [self indexPathForRowAtPoint: viewOrigin];
}
And the IBAction on the button:
- (void) buttonTapped: (UIButton *) sender {
NSIndexPath *indexPath = [self.tableView indexPathForView: sender];
NSLog(#"Button tapped at indexpPath [%ld-%ld]",
(long)indexPath.section,
(long)indexPath.row);
}
In Swift:
import UIKit
public extension UITableView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let origin = view.bounds.origin
let viewOrigin = self.convert(origin, from: view)
let indexPath = self.indexPathForRow(at: viewOrigin)
return indexPath
}
}
I added this as a file "UITableView+indexPathForView" to a test project to make sure I got everything correct. Then in the IBAction for a button that is inside a cell:
func buttonTapped(_ button: UIButton) {
let indexPath = self.tableView.indexPathForView(button)
print("Button tapped at indexPath \(indexPath)")
}
I made the extension work on any UIView, not just buttons, so that it's more general-purpose.
The nice thing about this extension is that you can drop it into any project and it adds the new indexPathForView(_:) function to all your table views without having do change your other code at all.
You are running into the issue of cell-reuse.
When you create a button for the view you set a tag to it, but then you override this tag to set the row number to it.
When the cell get's reused, because the row number is longer ROW_BUTTON_ACTION, you don't reset the tag to the correct row number and things go wrong.
Using a tag to get information out of a view is almost always a bad idea and is quite brittle, as you can see here.
As Matt has already said, walking the hierarchy is a better idea.
Also, your method doesn't need to be written in this way. If you create your own custom cell, then the code you use to create and add buttons and tags isn't needed, you can do it in a xib, a storyboard, or even in code in the class. Furthermore, if you use the dequeue method that takes the index path, you will always get either a recycled cell, or a newly created cell, so there is no need to check that the cell returned is not nil.

When the button in TableView cell pressed, how do I know the button of which row of cell pressed?

I have a custom TableView cell, and in that there's a button.
The table view have many rows used the same custom cell.
Now if the button of one of the cells pressed. I want to know which row of cell the button is in?
(1). In this method cellForRowAtIndexPath: , assign button with a tag.
For example:
cell.yourbutton.tag = indexPath.row;
(2). Add action for you button with same selector
[cell.yourbutton addTarget:self action:#selector(cellButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
(3). Then
-(void)cellButtonClicked:(UIButton*)sender
{
NSIndexPath *path = [NSIndexPath indexPathForRow:sender.tag inSection:0];
}
You can define your custom property and you can use it like below:-
#define kCustomProperty #"CustomProperty"
Associate your object with that custom property like below
objc_setAssociatedObject(yourButton,kCustomProperty , yourCellIndexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
Get your data using the same property and object like below
NSIndexPath *yourCellIndexPath = (NSIndexPath *)objc_getAssociatedObject(yourButton, kCustomProperty);
Its a kind of custom property you can create by coding if you don't want to use tag.

Add multiple buttons to IOS7 Tableview

How would I go about adding several buttons to a tableview to each cell? I need to add a comment and like button to each cell in my tableview and each button will have to be specfic to the row being clicked. How do I go about doing something like this ? Do i place a action inside of
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
or is there a way to programatically set a button to a ibaction from inside cellForRowAtIndexPath that allows me to send parameters?
You can create custom table view cell and add multiple button.
1. Create class that subclasses UITableviewcell with xib
2. In xib delete the view and drag a tableview cell into xib
3. Add multiple buttons in xib
4. Create IBOutlet for each button.
5. In your view controller import your CustomTableViewCell and in CellForRowAtIndexPath method add action for each method and set button tag as indexpath.row
6. Identify the clicked button's indexpath from it's tag
Refer this link
I think this is what you are asking...
[cell.button1 addTarget:self action:#selector(button1Pressed:) forControlEvents:UIControlEventTouchUpInside]; In the
receiver method you can get sender's tag
-(void)button1Pressed:(id)sender{
UIButton *button1 = (UIButton*)sender;
int selectedRow = sender.tag;
}
You need to subclass your UITableViewCell so you can customise it to your need.
There are a lot of tutorials about this. Depends if you are using Storyboard or not. Here is one of many: http://zeroheroblog.com/ios/how-to-create-simple-tableview-with-custom-cells

reuse issue in UITableView

I have UITableView which covers whole screen (480px).
Each cell is of height 300px.
I have total 5 rows.
Below is the code what I have used.
-(UITableViewCell *) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell"];
if (cell==nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MainCell"];
}
UIButton *myButton = (UIButton *)[cell viewWithTag:999999999];
int i = indexPath.row+1;
myButton.tag = i;
myButton.titleLabel.text = [NSString stringWithFormat:#"B - %d", i];
[myButton setTitle:[NSString stringWithFormat:#"B - %d", i] forState:UIControlStateNormal];
NSLog(#"tag set is %d & text for button is =====B-%d====", i,i);
[myButton addTarget:self action:#selector(btnSelected:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
- (int) tableView:(UITableView *) tableView numberOfRowsInSection:(NSInteger)section {
return 5;
}
-(IBAction)btnSelected:(id)sender {
UIButton *button = (UIButton *)sender;
NSLog(#"my tag after click is ===%d", [button tag]);
}
Now when I run this code, I was expecting each cell will have B - 1 to B - 5 written. However after B - 3, all I see is B - 1 and B- 2.
NSLog says as below.
2013-07-28 23:31:34.281 NewTest[1783:11303] tag set is 1 & text for button is =====B-1====
2013-07-28 23:31:34.284 NewTest[1783:11303] tag set is 2 & text for button is =====B-2====
Now when I scroll fully down, I get NSLog as below.
2013-07-28 23:31:34.281 NewTest[1783:11303] tag set is 1 & text for button is =====B-1====
2013-07-28 23:31:34.284 NewTest[1783:11303] tag set is 2 & text for button is =====B-2====
2013-07-28 23:32:03.643 NewTest[1783:11303] tag set is 3 & text for button is =====B-3====
2013-07-28 23:32:03.719 NewTest[1783:11303] tag set is 4 & text for button is =====B-4====
2013-07-28 23:32:03.835 NewTest[1783:11303] tag set is 5 & text for button is =====B-5====
Now when tag and text are set properly why I see last two buttons as B-1 and B-2 instead of B-4 and B-5.
Any idea how to solve this problem?
Screenshot 1
Screenshot 2
Screenshot 3
Any idea how to solve this problem so that I have B-1 to B-5 written?
Note : If I decrease the height of the cell to 100, I see all button text as B-1 to B-5.
This question is related to my old question, but this is simpler version.
Sample Project
What I did is, not used tag and using accessibilityValue, I am fetching the button clicked id.
As you scroll down, the table view will reuse the cells that are no longer visible as it needs to display additional cells. The first time this happens is for cell "B-3". Because the table view is reusing a cell, the tag for the button in the cell was already previously set by your code to some number (probably 1). Thus, viewWithTag:999999999 will return nil. Your NSLog statements will make it look like things are working correctly, but actually you're trying to set a title on a nil button, thus the button in the reused cell does not get updated with the correct title.
For a solution: you could create a custom subclass of UITableViewCell with a property:
#property (nonatomic, weak) IBOutlet UIButton *button;
And in the storyboard or xib wire this up to your button. Or if necessary you could programmatically do this in the cell's awakeFromNib or init method. Now you can directly reference cell.button instead of using [cell viewWithTag:9999999].

Make UITableViewCell behave like a real button

I want to make UITableViewCell to behave like real button.
Until know I have been using the
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath`
trick, but this is not optimal because it doesn't behave like a button on tap/drag/release.
If you tap a cell row and drag your finger over the cell, it will not get selected when you release your finger (but a button would launch its action in the same case).
Is there any simple way of making a UITableViewCell to behave like a real button without resorting to insert an actual UIButton inside the cell?
You can just create table view cells with a button in them, set the buttons tag to the row so you can workout which row the button belongs to when you receive the button event. Just make sure you reset the buttons tag when you return a reused table view cell instead of creating a new one.
Subclass the UITableViewCell and use the following method:
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
[super setHighlighted:highlighted animated:animated];
if (highlighted) {
_backgroundImageView.image = [UIImage imageNamed:#"img_h"];
}
else {
_backgroundImageView.image = [UIImage imageNamed:#"img"];
}
}
Where img is a plain image and img_h is a highlighted version of that image.
One way is to create a UIButtton of size of your cell and added it to the cell.
Or else you could simply add a UITapGestureRecognizer to your UITableViewCell and that will do the work for you.

Resources