Jean Paul
Jean Paul

Reputation: 2439

How to know the UITableview row number

I have a UITableViewCell with UISwitch as accessoryview of each cell. When I change the value of the switch in a cell, how can I know in which row the switch is? I need the row number in the switch value changed event.

Upvotes: 39

Views: 17680

Answers (10)

ambientlight
ambientlight

Reputation: 7332

Accepted solution is a clever hack.

However why do we need to use hitpoint if we can utilize already available tag property on UIView? You would say that tag can store only either row or section since its a single Int.

Well... Don't forget your roots guys (CS101). A single Int can store two twice-smaller size integers. And here is an extension for this:

extension Int {

    public init(indexPath: IndexPath) {
        var marshalledInt: UInt32 = 0xffffffff

        let rowPiece = UInt16(indexPath.row)
        let sectionPiece = UInt16(indexPath.section)
        marshalledInt = marshalledInt & (UInt32(rowPiece) << 16)
        marshalledInt = marshalledInt + UInt32(sectionPiece)

        self.init(bitPattern: UInt(marshalledInt))
    }

    var indexPathRepresentation: IndexPath {
        let section = self & 0x0000ffff

        let pattern: UInt32 = 0xffff0000
        let row = (UInt32(self) & pattern) >> 16
        return IndexPath(row: Int(row), section: Int(section))
    }
}

In your tableView(_:, cellForRowAt:) you can then:

cell.yourSwitch.tag = Int(indexPath: indexPath)

And then in the action handler you would can:

func didToogle(sender: UISwitch){
    print(sender.tag.indexPathRepresentation)
}

However please note it's limitation: row and section need to be not larger then 65535. (UInt16.max)

I doubt your tableView's indexes will go that high but in case they do, challenge yourself and implement more efficient packing scheme. Say if we have a section very small, we don't need all 16 bits to represent a section. We can have our int layout like:

{section area length}{all remaining}[4 BITS: section area length - 1]

that is our 4 LSBs indicate the length of section area - 1, given that we allocate at least 1 bit for a section. Thus in case of our section is 0, the row can occupy up to 27 bits ([1][27][4]), which definitely should be enough.

Upvotes: 3

jrturton
jrturton

Reputation: 119242

Tags, subclasses, or view hierarchy navigation are too much work!. Do this in your action method:

CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.tableView]; 
NSIndexPath *hitIndex = [self.tableView indexPathForRowAtPoint:hitPoint];

Works with any type of view, multi section tables, whatever you can throw at it - as long as the origin of your sender is within the cell's frame (thanks rob!), which will usually be the case.

And here it is in a UITableView Swift extension:

extension UITableView {
    func indexPath(for view: UIView) -> IndexPath? {
        let location = view.convert(CGPoint.zero, to: self)
        return self.indexPathForRow(at: location)
    }
}

Upvotes: 160

danh
danh

Reputation: 62676

The accepted answer on this post is perfectly fine. I'd like to suggest to readers that the following, derived from @robmayoff on this post, is also perfectly fine:

- (NSIndexPath *)indexPathForView:(UIView *)view inTableView:(UITableView *)tableView {
    while (view && ![view isKindOfClass:[UITableViewCell class]])
        view = view.superview;
    UITableViewCell *cell = (UITableViewCell *)view;
    return [tableView indexPathForCell:cell];
}

Some have asserted that this approach contains too much computational work because of the while loop. The alternative, convert the view's origin to table view coordinate space and call indexPathForRowAtPoint:, hides even more work.

Some have asserted that this approach is unsafe relative to potential SDK changes. In fact, Apple has already changed the tableview cell hierarchy once, adding a contentView to the cell. This approach works before and after such a change. As long as view ancestors can be found via a chain of superviews (which is as fundamental as anything in UIKit), this is good code.

Upvotes: 1

Smit
Smit

Reputation: 1

i dont know about the multiple sections but i can give you for the one section...

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index=indexPath.row;
NSString *string=[[NSString alloc]initWithFormat:@"%ld",(long)index];
}

from this you can get the row number and you can save it to the string....

Upvotes: 0

serj
serj

Reputation: 301

One more variant of using superView. Works like category for UIView.

- (UITableViewCell *)superCell
{
    if (!self.superview) {
        return nil;
    }

    if ([self.superview isKindOfClass:[UITableViewCell class]]) {
        return (UITableViewCell *)self.superview;
    }

    return [self.superview superCell];
}

Upvotes: 0

jimkberry
jimkberry

Reputation: 1327

A colleague suggested the following, which I made into a UITableView category:

+(UITableViewCell*)findParentCellForSubview:(UIView*)view
{
    while (([view isKindOfClass:[UITableViewCell class]] == NO) && ([view superview] != nil))
        view = [view superview];

    if ([view superview] != nil)
        return (UITableViewCell*)view;

    return nil;
}

Still hackly - but it works.

Upvotes: 0

Andres Canella
Andres Canella

Reputation: 3716

I prefer using subviews, if you know your layout it's generally super simple and 1 line short...

    NSIndexPath *indexPath = [tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview]];

Thats it, if its more nested, add in more superviews.

Bit more info:

all you are doing is asking for the parent view and its parent view which is the cell. Then you are asking your tableview for the indexpath of that cell you just got.

Upvotes: 2

rob mayoff
rob mayoff

Reputation: 385610

If you set the tag property to the row number (as suggested by other answers), you have to update it every time in tableView:cellForRowAtIndexPath: (because a cell can be reused for different rows).

Instead, when you need the row number, you can walk up the superview chain from the UISwitch (or any other view) to the UITableViewCell, and then to the UITableView, and ask the table view for the index path of the cell:

static NSIndexPath *indexPathForView(UIView *view) {
    while (view && ![view isKindOfClass:[UITableViewCell class]])
        view = view.superview;
    if (!view)
        return nil;
    UITableViewCell *cell = (UITableViewCell *)view;
    while (view && ![view isKindOfClass:[UITableView class]])
        view = view.superview;
    if (!view)
        return nil;
    UITableView *tableView = (UITableView *)view;
    return [tableView indexPathForCell:cell];
}

This doesn't require anything in tableView:cellForRowAtIndexPath:.

Upvotes: 5

Daniel Rinser
Daniel Rinser

Reputation: 8855

One common way to do this is to set the tag of the control (in your case the switch) to something that can be used to identify the row or represented object.

For example, in tableView:cellForRowAtIndexPath: set the tag property of the switch to the indexPath.row and in your action method you can get the tag from the sender.

Personally, I don't like this approach and prefer subclassing UITableViewCell. Also, it may be a good idea to add an "offset" to the tag to prevent any conflicts with the tags of other views.

Upvotes: 1

wattson12
wattson12

Reputation: 11174

in cellForRowAtIndexPath:, set the tag property of your control to indexPath.row

Upvotes: 3

Related Questions