Obtenez un clic sur le bouton dans UITableViewCell


140

J'ai un contrôleur de vue avec une vue de tableau et une pointe distincte pour le modèle de cellule de tableau. Le modèle de cellule a quelques boutons. Je veux accéder au bouton cliquez avec l'index de la cellule cliquée dans le contrôleur de vue où j'ai défini la vue Table.

Donc j'ai ViewController.het ViewController.moù j'ai le UITableViewet TableTemplate.h, TableTemplate.met TableTemplate.xiboù j'ai défini la plume. Je veux l'événement de clic sur le bouton avec l'index de la cellule ViewController.m.

Une aide sur comment puis-je faire cela?

Réponses:


258

1) Dans votre cellForRowAtIndexPath:méthode, attribuez une balise de bouton comme index:

cell.yourbutton.tag = indexPath.row;

2) Ajoutez une cible et une action pour votre bouton comme ci-dessous:

[cell.yourbutton addTarget:self action:@selector(yourButtonClicked:) forControlEvents:UIControlEventTouchUpInside];

3) Actions de code basées sur l'index comme ci-dessous dans ViewControler:

-(void)yourButtonClicked:(UIButton*)sender
{
     if (sender.tag == 0) 
     {
         // Your code here
     }
}

Mises à jour pour plusieurs sections:

Vous pouvez vérifier ce lien pour détecter le clic de bouton dans la vue tableau pour plusieurs lignes et sections.


1
Cela peut également être fait via Interface Builder (IB) à la deuxième étape. Assurez-vous simplement que votre balise de boutons est définie. Vous ne voulez vraiment pas mélanger vos appels à l'action. Faites-le via IB ou faites-le explicitement dans votre code.
Sententia

@Mani Cela ne casse pas MVC - l'action est dans la TableView et non dans la cellule.
davecom

@davecom Si vous définissez la cible du bouton comme cellule (via IB), comment sera-t-elle déclenchée à partir de tableView? Ou existe-t-il un moyen de connecter la cible du bouton à tableview qui est placée dans le xib de la cellule?
Mani

24
Cette solution rencontre des problèmes lorsque vous commencez à insérer et à supprimer des lignes. La balise n'est pas mise à jour lorsque les lignes sont décalées. Au lieu de garder une référence à la ligne. Il peut être préférable de conserver une référence à un identifiant d'objet unique.
Vincent Cheong

1
Chaque fois que vous attribuez des valeurs aux attributs de balise des vues, vous avez une très mauvaise odeur de code qui peut vous mordre plus tard. Cherchez de meilleures façons d'atteindre votre objectif, pas le premier message SO que vous trouvez.
TigerCoding

148

Les délégués sont la voie à suivre.

Comme vu avec d'autres réponses, l'utilisation de vues peut devenir obsolète. Qui sait demain il y aura peut-être un autre wrapper et devra peut-être l'utiliser cell superview]superview]superview]superview]. Et si vous utilisez des balises, vous obtiendrez un nombre n de conditions if else pour identifier la cellule. Pour éviter tout cela, configurez des délégués. (Ce faisant, vous allez créer une classe de cellule réutilisable. Vous pouvez utiliser la même classe de cellule comme classe de base et tout ce que vous avez à faire est d'implémenter les méthodes déléguées.)

Nous avons d'abord besoin d'une interface (protocole) qui sera utilisée par la cellule pour communiquer les clics de bouton (déléguer). ( Vous pouvez créer un fichier .h distinct pour le protocole et l'inclure à la fois dans le contrôleur de vue table et dans les classes de cellules personnalisées OU simplement l'ajouter dans la classe de cellule personnalisée qui sera de toute façon incluse dans le contrôleur de vue table )

@protocol CellDelegate <NSObject>
- (void)didClickOnCellAtIndex:(NSInteger)cellIndex withData:(id)data;
@end

Incluez ce protocole dans le contrôleur de vue de cellule et de tableau personnalisé. Et assurez-vous que le contrôleur de vue de table confirme ce protocole.

Dans une cellule personnalisée, créez deux propriétés:

@property (weak, nonatomic) id<CellDelegate>delegate;
@property (assign, nonatomic) NSInteger cellIndex;

Dans le UIButtondélégué IBAction, cliquez sur: ( même chose peut être faite pour toute action dans la classe de cellule personnalisée qui doit être déléguée au contrôleur de vue )

- (IBAction)buttonClicked:(UIButton *)sender {
    if (self.delegate && [self.delegate respondsToSelector:@selector(didClickOnCellAtIndex:withData:)]) {
        [self.delegate didClickOnCellAtIndex:_cellIndex withData:@"any other cell data/property"];
    }
}

Dans le contrôleur de vue de table cellForRowAtIndexPath après avoir retiré la cellule, définissez les propriétés ci-dessus.

cell.delegate = self;
cell.cellIndex = indexPath.row; // Set indexpath if its a grouped table.

Et implémentez le délégué dans le contrôleur de vue table:

- (void)didClickOnCellAtIndex:(NSInteger)cellIndex withData:(id)data
{
    // Do additional actions as required.
    NSLog(@"Cell at Index: %d clicked.\n Data received : %@", cellIndex, data);
}

Ce serait l'approche idéale pour obtenir des actions de bouton de cellule personnalisées dans le contrôleur de vue tableau.


2
Pourquoi avez-vous fait du délégué une propriété forte de la cellule? Cela vous donnera un cycle de rétention, sauf si vous savez que le contrôleur ne tient que faiblement la cellule.
JulianSymes

qu'en est-il du _cellIndex beign mis à jour après la suppression de la cellule?
skornos

2
J'ai entendu un de mes amis dire que l'utilisation d'un délégué sur chaque cellule entraîne une consommation de mémoire, alors utilisez des balises. Est-ce vrai?
Bista

2
vérifier cette question dude stackoverflow.com/questions/31649220/…
Nischal Hada

@the_UB Il ne peut pas y avoir grand-chose entre la définition d'un tag et le stockage d'une seule référence. Peut-être qu'une balise prendrait plus de mémoire.
Ian Warburton

66

Au lieu de jouer avec les tags, j'ai adopté une approche différente. Délégué créé pour ma sous-classe de UITableViewCell (OptionButtonsCell) et ajouté un indexPath var. À partir de mon bouton dans le storyboard, j'ai connecté @IBAction à OptionButtonsCell et là, j'envoie la méthode de délégué avec le bon indexPath à toute personne intéressée. Dans la cellule pour le chemin d'index, j'ai défini indexPath actuel et cela fonctionne :)

Laissez le code parler de lui-même:

Swift 3 Xcode 8

OptionButtonsTableViewCell.swift

import UIKit
protocol OptionButtonsDelegate{
    func closeFriendsTapped(at index:IndexPath)
}
class OptionButtonsTableViewCell: UITableViewCell {
    var delegate:OptionButtonsDelegate!
    @IBOutlet weak var closeFriendsBtn: UIButton!
    var indexPath:IndexPath!
    @IBAction func closeFriendsAction(_ sender: UIButton) {
        self.delegate?.closeFriendsTapped(at: indexPath)
    }
}

MyTableViewController.swift

class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate {...

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "optionCell") as! OptionButtonsTableViewCell
    cell.delegate = self
    cell.indexPath = indexPath
    return cell   
}

func closeFriendsTapped(at index: IndexPath) {
     print("button tapped at index:\(index)")
}

pouvez-vous m'aider, class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate j'obtiens une erreur à cette ligne: // erreur: conformité redondante de 'MyTableViewController' au protocole 'UITableViewDataSource'
Ulug'bek Ro'zimboyev

semble que vous essayez de vous conformer à UITableViewDataSource plusieurs fois. Peut-être avez-vous une extension où vous vous conformez déjà à la source de données ?, ne peut pas aider plus sans code
Maciej Chrzastek

1
et comment transmettre des données pour effectuer une segmentation et accéder à un autre contrôleur de vue?
Milad Faridnia

2
La meilleure solution et la plus propre!
appsunited

31

Cela devrait aider: -

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

Ici, l' expéditeur est l'instance UIButton qui envoie l'événement. myTableView est l'instance UITableView avec laquelle vous traitez.

Obtenez simplement la bonne référence de cellule et tout le travail est terminé.

Vous devrez peut-être supprimer les boutons du contentView de la cellule et les ajouter directement à l'instance UITableViewCell en tant que sous-vue.

Ou

Vous pouvez formuler un schéma de dénomination des balises pour différents UIButtons dans cell.contentView. En utilisant cette balise, vous pourrez ultérieurement connaître les informations de ligne et de section selon vos besoins.


4
ce devrait être [[superview de l'expéditeur] superview];
pierre23

2
C'est bon pour les cellules très simples. Cependant, si votre cellule a un arbre profond de vues, la réponse de Mani est la meilleure.
Sententia

3
Maintenant dans iOS 7, il devrait être UITableViewCell * cell = (UITableViewCell *) [[[sender superview] superview] superview]; Je vous remercie.
Rajan Maharjan


22

Le code suivant pourrait vous aider.

J'ai pris UITableViewavec une classe de cellule prototype personnalisée nommée à l' UITableViewCellintérieurUIViewController .

Donc j'ai ViewController.h, ViewController.met TableViewCell.h,TableViewCell.m

Voici le code pour cela:

ViewController.h

@interface ViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>

@property (strong, nonatomic) IBOutlet UITableView *tblView;

@end

ViewController.m

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return (YourNumberOfRows);
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    static NSString *cellIdentifier = @"cell";

    __weak TableViewCell *cell = (TableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

    if (indexPath.row==0) {
        [cell setDidTapButtonBlock:^(id sender)
         {
             // Your code here

         }];
    }    
    return cell;
}

Classe de cellule personnalisée:

TableViewCell.h

@interface TableViewCell : UITableViewCell

@property (copy, nonatomic) void (^didTapButtonBlock)(id sender);

@property (strong, nonatomic) IBOutlet UILabel *lblTitle;
@property (strong, nonatomic) IBOutlet UIButton *btnAction;

- (void)setDidTapButtonBlock:(void (^)(id sender))didTapButtonBlock;

@end

et

UITableViewCell.m

@implementation TableViewCell

- (void)awakeFromNib {
    // Initialization code
    [self.btnAction addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];

}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}
- (void)didTapButton:(id)sender {
    if (self.didTapButtonBlock)
    {
        self.didTapButtonBlock(sender);
    }
}

Remarque : Ici, j'ai tout pris en UIControlsutilisant Storyboard.

J'espère que cela peut vous aider ... !!!


The best way ever
Daniel Raouf

15

La raison pour laquelle j'aime la technique ci-dessous, car elle m'aide également à identifier la section du tableau.

Ajouter un bouton dans la cellule cellForRowAtIndexPath:

 UIButton *selectTaskBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        [selectTaskBtn setFrame:CGRectMake(15, 5, 30, 30.0)];
        [selectTaskBtn setTag:indexPath.section]; //Not required but may find useful if you need only section or row (indexpath.row) as suggested by MR.Tarun 
    [selectTaskBtn addTarget:self action:@selector(addTask:)   forControlEvents:UIControlEventTouchDown];
[cell addsubview: selectTaskBtn];

AddTask d'événement:

-(void)addTask:(UIButton*)btn
{
    CGPoint buttonPosition = [btn convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     int currentIndex = indexPath.row;
     int tableSection = indexPath.section;
    }
}

Espère cette aide.



12

Utilisez les fermetures Swift:

class TheCell: UITableViewCell {

    var tapCallback: (() -> Void)?

    @IBAction func didTap(_ sender: Any) {
        tapCallback?()
    }
}

extension TheController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: TheCell.identifier, for: indexPath) as! TheCell {
            cell.tapCallback = {
                //do stuff
            }
            return cell
    }
}

7

Le code de Tarun ne fonctionne pas sur iOS7, car la structure UITableViewCell a changé et maintenant il obtiendrait "UITableViewCellScrollView" à la place.

Cet article Obtenir UITableViewCell avec superview dans iOS 7 a une bonne solution pour créer une boucle pour trouver la vue parent correcte, indépendamment des modifications futures de la structure. Cela revient à créer une boucle:

    UIView *superView = [sender superview];
    UIView *foundSuperView = nil;

    while (nil != superView && nil == foundSuperView) {
        if ([superView isKindOfClass:[UITableViewCell class]]) {
            foundSuperView = superView;
        } else {
            superView = superView.superview;
        }
    }

Le lien contient du code pour une solution plus réutilisable, mais cela devrait fonctionner.


6

Swift 2.2

Vous devez ajouter une cible pour ce bouton.

myButton.addTarget(self, action: #selector(ClassName.FunctionName(_:), forControlEvents: .TouchUpInside)

FunctionName: connecté // par exemple

Et bien sûr, vous devez définir la balise de ce bouton puisque vous l'utilisez.

myButton.tag = indexPath.row

Vous pouvez y parvenir en sous-classant UITableViewCell. Utilisez-le dans le générateur d'interface, déposez un bouton sur cette cellule, connectez-le via une prise et c'est parti.

Pour obtenir le tag dans la fonction connectée:

func connected(sender: UIButton) {
    let buttonTag = sender.tag
    // Do any additional setup
}

6

Swift 3 avec une fermeture

Une bonne solution consiste à utiliser une fermeture dans un UITableViewCell personnalisé pour rappeler au viewController pour une action.

Dans la cellule:

final class YourCustomCell: UITableViewCell {

    var callbackClosure: (() -> Void)?

    // Configure the cell here
    func configure(object: Object, callbackClosure: (() -> Void)?) {
       self.callbackClosure = callbackClosure
    }


// MARK: - IBAction
extension YourCustomCell {
    @IBAction fileprivate func actionPressed(_ sender: Any) {
        guard let closure = callbackClosure else { return }
        closure()
    }
}

Contrôleur In View: Délégué Tableview

extension YourViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        guard let cell: YourCustomCell = cell as? YourCustomCell else { return }
        cell.configure(object: object, callbackClosure: { [weak self] in
            self?.buttonAction()
        })
     }
 }

fileprivate extension YourViewController {

    func buttonAction() {
        // do your actions here 
    }
}

5

Je trouve plus simple de sous-classer le bouton à l'intérieur de votre cellule (Swift 3):

class MyCellInfoButton: UIButton {
    var indexPath: IndexPath?
}

Dans votre classe de cellule:

class MyCell: UICollectionViewCell {
    @IBOutlet weak var infoButton: MyCellInfoButton!
   ...
}

Dans la source de données de la vue table ou de la vue collection, lors du retrait de la cellule, attribuez au bouton son chemin d'index:

cell.infoButton.indexPath = indexPath

Vous pouvez donc simplement mettre ce code dans votre contrôleur de vue table:

@IBAction func handleTapOnCellInfoButton(_ sender: MyCellInfoButton) {
        print(sender.indexPath!) // Do whatever you want with the index path!
}

Et n'oubliez pas de définir la classe du bouton dans votre Interface Builder et de la lier à la handleTapOnCellInfoButtonfonction!


édité:

Utilisation de l'injection de dépendances. Pour configurer l'appel d'une fermeture:

class MyCell: UICollectionViewCell {
    var someFunction: (() -> Void)?
    ...
    @IBAction func didTapInfoButton() {
        someFunction?()
    }
}

et injectez la fermeture dans la méthode willDisplay du délégué de la vue de collection:

 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    (cell as? MyCell)?.someFunction = {
        print(indexPath) // Do something with the indexPath.
    }
}

L'approche de fermeture est la manière la plus Swifty que j'ai vue de faire cela. Bon travail!
Clifton Labrum

5

Son travail pour moi.

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     UIButton *Btn_Play = (UIButton *)[cell viewWithTag:101];
     [Btn_Play addTarget:self action:@selector(ButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)ButtonClicked:(UIButton*)sender {
     CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.Tbl_Name];
     NSIndexPath *indexPath = [self.Tbl_Name indexPathForRowAtPoint:buttonPosition];
}

1
// Add action in cell for row at index path -tableView

cell.buttonName.addTarget(self, action: #selector(ViewController.btnAction(_:)), for: .touchUpInside)

// Button Action

  @objc func btnAction(_ sender: AnyObject) {



        var position: CGPoint = sender.convert(.zero, to: self.tableView)


        let indexPath = self.tableView.indexPathForRow(at: position)
        let cell: UITableViewCell = tableView.cellForRow(at: indexPath!)! as
        UITableViewCell




}

1

pour swift 4:

inside the cellForItemAt ,
   
cell.chekbx.addTarget(self, action: #selector(methodname), for: .touchUpInside)

then outside of cellForItemAt
@objc func methodname()
{
//your function code
}


1

Si vous souhaitez passer la valeur du paramètre de la cellule à UIViewController en utilisant la fermeture, alors

//Your Cell Class
class TheCell: UITableViewCell {

    var callBackBlockWithParam: ((String) -> ()) = {_ in }

//Your Action on button
    @IBAction func didTap(_ sender: Any) {
        callBackBlockWithParam("Your Required Parameter like you can send button as sender or anything just change parameter type. Here I am passing string")
    }
}

//Your Controller
extension TheController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: TheCell.identifier, for: indexPath) as! TheCell {
            cell.callBackBlockWithParam = { (passedParamter) in 

             //you will get string value from cell class
                print(passedParamter)     
      }
            return cell
    }
}

0

La réponse @Mani est bonne, mais les balises de vues à l'intérieur de contentView de la cellule sont souvent utilisées à d'autres fins. Vous pouvez utiliser la balise de cellule à la place (ou la balise contentView de la cellule):

1) Dans votre cellForRowAtIndexPath:méthode, attribuez la balise de la cellule comme index:

cell.tag = indexPath.row; // or cell.contentView.tag...

2) Ajoutez une cible et une action pour votre bouton comme ci-dessous:

[cell.yourbutton addTarget:self action:@selector(yourButtonClicked:) forControlEvents:UIControlEventTouchUpInside];

3) Créez une méthode qui renvoie la ligne de l'expéditeur (merci @Stenio Ferreira):

- (NSInteger)rowOfSender:(id)sender
{
    UIView *superView = sender.superview;
    while (superView) {
        if ([superView isKindOfClass:[UITableViewCell class]])
            break;
        else
            superView = superView.superview;
    }

    return superView.tag;
}

4) Actions de code basées sur l'index:

-(void)yourButtonClicked:(UIButton*)sender
{
     NSInteger index = [self rowOfSender:sender];
     // Your code here
}

0

CustomTableCell.h est un UITableViewCell:

@property (weak, nonatomic) IBOutlet UIButton *action1Button;
@property (weak, nonatomic) IBOutlet UIButton *action2Button;

MyVC.m après les importations:

@interface MYTapGestureRecognizer : UITapGestureRecognizer
@property (nonatomic) NSInteger dataint;
@end

À l'intérieur de "cellForRowAtIndexPath" dans MyVC.m:

//CustomTableCell 
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

//Set title buttons
[cell.action1Button setTitle:[NSString stringWithString:NSLocalizedString(@"action1", nil)] forState:UIControlStateNormal];
[cell.action2Button setTitle:[NSString stringWithString:NSLocalizedString(@"action2", nil)] forState:UIControlStateNormal];

//Set visibility buttons
[cell.action1Button setHidden:FALSE];
[cell.action2Button setHidden:FALSE];

//Do 1 action
[cell.action1Button addTarget:self action:@selector(do1Action :) forControlEvents:UIControlEventTouchUpInside];

//Do 2 action
MYTapGestureRecognizer *action2Tap = [[MYTapGestureRecognizer alloc] initWithTarget:self action:@selector(do2Action :)];
cancelTap.numberOfTapsRequired = 1;
cancelTap.dataint = indexPath.row;
[cell.action2Button setUserInteractionEnabled:YES];
[cell.action2Button addGestureRecognizer:action2Tap];

MyVC.m:

-(void)do1Action :(id)sender{
//do some action that is not necessary fr data
}

-(void)do2Action :(UITapGestureRecognizer *)tapRecognizer{
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
numberTag = tap.dataint;
FriendRequest *fr = [_list objectAtIndex:numberTag];

//connect with a WS o do some action with fr data

//actualize list in tableView
 [self.myTableView reloadData];
}

-1
cell.show.tag=indexPath.row;
     [cell.show addTarget:self action:@selector(showdata:) forControlEvents:UIControlEventTouchUpInside];

-(IBAction)showdata:(id)sender
{
    UIButton *button = (UIButton *)sender;

    UIStoryboard *storyBoard;
    storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    SecondViewController *detailView = [storyBoard instantiateViewControllerWithIdentifier:@"SecondViewController"];

    detailView.string=[NSString stringWithFormat:@"%@",[_array objectAtIndex:button.tag]];

    [self presentViewController:detailView animated:YES completion:nil];

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.