Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 695
CGR Avatar asked Feb 03 '26 07:02

CGR


2 Answers

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).]

like image 93
matt Avatar answered Feb 05 '26 20:02

matt


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.

like image 38
Duncan C Avatar answered Feb 05 '26 21:02

Duncan C



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!