21

i want to animate the UICollectionViewCell when action is called.
i have done UICollectionViewCell in Interface Builder, the UICollectionView also. Now i want to get the correct indexPath at my actionBtnAddToCard method.

thats the way i try it now (method in ProduktViewCell.m):

- (IBAction)actionAddToCart:(id)sender {
    XLog(@"");

    // see this line
    NSIndexPath *indexPath = ??** how can i access the correct indexPath**??;
    SortimentViewController *svc = [[SortimentViewController alloc] initWithNibName:@"SortimentViewController_iPad" bundle:[NSBundle mainBundle]];
    [svc.collectionViewProdukte cellForItemAtIndexPath:indexPath];

    [svc collectionView:svc.collectionViewProdukte didSelectItemAtIndexPath:indexPath];
} 

SortimentViewController is the viewController which inherits the UICollectionView.
how to acces the correct indexPath?

UPDATE 1: edited post for better understanding.

brush51
  • 5,691
  • 6
  • 39
  • 73
  • 1
    Have a look at this SO answer which is a more elegant and simple way http://stackoverflow.com/questions/13966291/achieve-button-click-in-uicollectionview?answertab=votes#tab-top – Mingming Apr 18 '13 at 07:34

13 Answers13

25
- (IBAction)actionAddToCart:(id)sender {
   NSIndexPath *indexPath;
   indexPath = [self.collectionView indexPathForItemAtPoint:[self.collectionView convertPoint:sender.center fromView:sender.superview]]; 
   ...
}
Carmen
  • 6,177
  • 1
  • 35
  • 40
16

if you know the view hierarchy it is easy.

UIButton *button = (UiButton *) sender;

if the button is like this - > UITableViewCell - > button

then you can get cell like this

UITableViewCell *cell = (UITableViewCell *)[button superview];

if the button is like this - > UITableViewCell - > content view -> button

UITableViewCell *cell = (UITableViewCell *)[[button superview] superview];

and finally index path can be extracted like this

NSIndexPath *indexPath = [self.table_View indexPathForCell:cell];
Shubhank
  • 21,721
  • 8
  • 65
  • 83
  • yeah , bit confused what u mean when talking in the context of a collection view, would you mind shoing us a full action snippet? – ChuckKelly Jan 25 '14 at 03:42
  • 28
    The OP had asked for a solution in the case for UICollectionView not UITableViewCell.. IMHO this answer sends the wrong message to readers.. Please edit to point how to do this in UICollectionView. – iSeeker May 02 '14 at 09:48
  • 1
    `[[button superview] superview]` is fragile. I added a safer answer below. – Echelon Apr 27 '15 at 16:54
  • UITableViewCell *cell = (UITableViewCell *)[[button superview] superview]; help me.when I have: button->collectionview->UItableviewcell – lee Oct 28 '15 at 07:24
  • 1
    Sorry but calling superview a bunch of times is very hacky. Changes to the view hierarchy could easily break such calls. And this original question does not deal with UITableView. – Brian Sachetta Mar 25 '16 at 15:41
  • UICollectionView supports the indexPathForCell: method as well. If you update this answer to reference UICollectionView / UICollectionViewCell rather than UITableView / UITableViewCell I will upvote. – Brian Sachetta Mar 25 '16 at 15:48
  • superview of a superview is a dangerous hack. i know because i use it – iRon111 Feb 20 '20 at 00:29
7

Do Not Depend on view. Try this.

CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:buttonPosition];

NSLog(@"%ld", (long)indexPath.row);
Ego Slayer
  • 1,987
  • 2
  • 22
  • 17
6

Using code like [[button superview] superview] is fragile and not future-proof; indeed, it's not even guaranteed to work on all iOS versions unless you explicitly test it. I always use an iterative helper method for this purpose:-

- (UIView *)superviewWithClassName:(NSString *)className fromView:(UIView *)view
{
    while (view)
    {
        if ([NSStringFromClass([view class]) isEqualToString:className])
        {
            return view;
        }
        view = view.superview;
    }
    return nil;
}

Then I call it from the button handler like so:-

- (IBAction)buttonClicked:(id)sender
{
    UIButton *button = (UIButton *)sender;
    UICollectionViewCell *cell = (UICollectionViewCell *)
                                [self superviewWithClassName:@"UICollectionViewCell"
                                fromView:button];
    if (cell)
    {
        NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
        // do whatever action you need with the indexPath...
    }
}

UPDATE: Swift version of superviewWithClassName. Made it a class method since it never references self.

static func superviewWithClassName(className:String, fromView view:UIView?) -> UIView? {
    guard let classType = NSClassFromString(className) else {
        return nil
    }
    var v:UIView? = view
    while (v != nil) {
        if v!.isKindOfClass(classType) {
            return v
        }
        v = v!.superview
    }
    return nil
}

and some code to call it, either from prepareForSegue or a button handler:-

guard let cell = UIView.superviewWithClassName("UICollectionViewCell", fromView: sender as? UIView) as? UITableViewCell else {return}
Echelon
  • 7,306
  • 1
  • 36
  • 34
6

Swift solution: A UICollectionView extension like this one can be useful for this.

extension UICollectionView {
    func indexPathForView(view: AnyObject) -> NSIndexPath? {
        let originInCollectioView = self.convertPoint(CGPointZero, fromView: (view as! UIView))
        return self.indexPathForItemAtPoint(originInCollectioView)
    }
}

Usage becomes easy everywhere.

let indexPath = collectionView.indexPathForView(button)
Ishan Handa
  • 2,271
  • 20
  • 25
3

You can do it like this, indexPathsForVisibleItems will return array of NSIndexPaths for items currently visible on view and first object returns the first one (if you have one cell per view).

NSIndexPath *indexPath = [[svc.collectionViewProdukte indexPathsForVisibleItems] firstObject]
Markus
  • 686
  • 1
  • 11
  • 18
2

If you want to animate a specific cell, you need to get a reference to that cell. Simply calling

[svc.collectionViewProdukte cellForItemAtIndexPath:indexPath];

does nothing. You need to keep the cell that the method returns, like this:

UICollectionViewCell *cell = [svc.collectionViewProdukte cellForItemAtIndexPath:indexPath];

After that, go ahead and animate:

[UIView animateWithDuration:0.2f animations:^{
    cell.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
}];
Tim
  • 226
  • 1
  • 2
  • 1
    but how can i get the indexPath? i will update my question for better understanding – brush51 Dec 14 '12 at 09:23
  • An indexPath is just a way to refer to a specific cell. Without knowing what your app does, I have no way to guess which cell you are trying to animate. – Tim Dec 14 '12 at 09:27
  • To get the first cell, it would be something like this: NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; – Tim Dec 14 '12 at 09:28
  • 1
    thats my problem. How can i find out on which cells action is called? – brush51 Dec 14 '12 at 09:29
  • That really depends on the structure of your app. If each cell has an 'add to cart' button, you might think about putting that button inside the cell (which should be a UICollectionViewCell subclass), and make sure the button is calling a method on the cell itself (not the view controller). Then use a delegation pattern to call some logical method on the view controller: something like -(void)addToCartButtonPressedForCell:(UICollectionViewCell *)cell]; – Tim Dec 14 '12 at 09:36
2

Swift 3 Solution : Based on Ishan Handa's Answer

extension UICollectionView {
func indexPathForView(view: AnyObject) -> IndexPath? {
    let originInCollectioView = self.convert(CGPoint.zero, from: (view as! UIView))
    return self.indexPathForItem(at: originInCollectioView) as IndexPath?
  }
}

Usage:

func deleteCell(sender:UIButton){
    var indexPath:IndexPath? = nil
    indexPath = self.collectionView.indexPathForView(view: sender)        
    print("index path : \(indexPath)")
}
bpolat
  • 3,879
  • 20
  • 26
1
//Note: this is for a storyboard implementation

// here is code for finding the row and section of a textfield being edited in a uicollectionview
UIView *contentView = (UIView *)[textField superview];
UICollectionViewCell *cell = (UICollectionViewCell *)[contentView superview];
cell = (UICollectionViewCell *)[contentView superview];

// determine indexpath for a specific cell in a uicollectionview
NSIndexPath *editPath = [myCollectionView indexPathForCell:cell];
int rowIndex = editPath.row;
int secIndex = editPath.section;
RFAustin
  • 315
  • 2
  • 9
1

Even though many answer i found here .this will be shortest and useful irrespective of the view hierarchy

- (void) actionAddToCart:(id)sender
{
    id view = [sender superview];

    while (view && [view isKindOfClass:[UICollectionViewCell class]] == NO) 
    {
        view = [view superview];
    }
    NSIndexPath *thisIndexPath = [self.collectionView indexPathForCell:view];

    NSLog(@"%d actionAddToCart pressed",thisIndexPath.row);
}
P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
Vasu Ashok
  • 1,413
  • 3
  • 17
  • 37
1

Xcode10. Swift 4.2 version.

extension UICollectionView {

  func indexPathForView(view: AnyObject) -> IndexPath? {
      guard let view = view as? UIView else { return nil }
      let senderIndexPath = self.convert(CGPoint.zero, from: view)
      return self.indexPathForItem(at: senderIndexPath)
  }

}

Usage:

// yourView can be button for example
let indexPath = collectionView.indexPathForView(view: yourView) 
Alex Kolovatov
  • 859
  • 7
  • 12
0

You almost certainly have a UICollectionViewCell subclass. Just add a property and set the indexPath in cellForItemAtIndexPath.

arsenius
  • 12,090
  • 7
  • 58
  • 76
  • This works until you enable reordering of items, then you have to manually go in and fix indexPath property for all the affected cells. – Bryan Bryce Feb 03 '16 at 00:14
0
 internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: “cell_id”, for: indexPath)
    
    let bttn_obj = UIButton(frame: CGRect(x: 5.5, y: 5.5, width: 22, height: 22))
    bttn_obj.addTarget(self, action: #selector(bttn_action), for: UIControl.Event.touchUpInside)
    cell.addSubview(bttn_obj)        
    
    return cell
}

@IBAction func bttn_action(_ sender: UIButton) -> Void {
    
    let cell_view = sender.superview as! UICollectionViewCell
    let index_p : IndexPath = self.collectionview.indexPath(for: cell_view)!
    
    print(index_p)
 
}
Eliseo A.
  • 19
  • 2