Détecter quel UIButton a été pressé dans un UITableView

212

J'ai un UITableViewavec 5 UITableViewCells. Chaque cellule contient un UIButtonqui est configuré comme suit:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     NSString *identifier = @"identifier";
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
     if (cell == nil) {
         cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
         [cell autorelelase];

         UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 5, 40, 20)];
         [button addTarget:self action:@selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
         [button setTag:1];
         [cell.contentView addSubview:button];

         [button release];
     }

     UIButton *button = (UIButton *)[cell viewWithTag:1];
     [button setTitle:@"Edit" forState:UIControlStateNormal];

     return cell;
}

Ma question est la suivante: dans la buttonPressedAction:méthode, comment savoir quel bouton a été enfoncé. J'ai envisagé d'utiliser des balises, mais je ne suis pas sûr que ce soit le meilleur itinéraire. J'aimerais pouvoir en quelque sorte marquer le indexPathsur le contrôle.

- (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    // how do I know which button sent this message?
    // processing button press for this row requires an indexPath. 
}

Quelle est la manière standard de procéder?

Éditer:

Je l'ai un peu résolu en faisant ce qui suit. Je voudrais quand même avoir une opinion sur le fait que ce soit la façon standard de le faire ou y a-t-il une meilleure façon?

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     NSString *identifier = @"identifier";
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
     if (cell == nil) {
         cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
         [cell autorelelase];

         UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 5, 40, 20)];
         [button addTarget:self action:@selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
         [cell.contentView addSubview:button];

         [button release];
     }

     UIButton *button = (UIButton *)[cell.contentView.subviews objectAtIndex:0];
     [button setTag:indexPath.row];
     [button setTitle:@"Edit" forState:UIControlStateNormal];

     return cell;
}

- (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    int row = button.tag;
}

Ce qui est important à noter, c'est que je ne peux pas définir la balise dans la création de la cellule car la cellule peut être retirée de la file d'attente à la place. C'est très sale. Il doit y avoir un meilleur moyen.

rêne
la source
Je ne vois aucun problème avec l'utilisation de votre solution de balises. Les cellules sont réutilisées, il est donc logique de définir la balise sur l'index de ligne comme vous le faites ici. Je trouve que c'est une solution beaucoup plus élégante que de convertir l'emplacement tactile en un index de ligne, comme suggéré ci-dessous.
Erik van der Neut

Réponses:

400

Dans l' exemple d' accessoire d'Apple, la méthode suivante est utilisée:

[button addTarget:self action:@selector(checkButtonTapped:) forControlEvents:UIControlEventTouchUpInside];

Ensuite, dans le gestionnaire tactile, les coordonnées tactiles sont récupérées et le chemin d'index est calculé à partir de ces coordonnées:

- (void)checkButtonTapped:(id)sender
{
    CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     ...
    }
}
Vladimir
la source
Oui, c'est ce sur quoi je me suis installé (voir mon montage). Je suis d'accord avec vous que ce n'est pas optimal.
rêver
2
Mais vous ajoutez vous-même UIButton à UITableViewCell, vous devez donc être cohérent avec ce que vous faites lors de la création d'une cellule. Bien que cette approche ne semble pas vraiment élégante, je dois admettre
Vladimir
1
Pour la première solution, vous devrez saisir [[button superview] superview] car le premier appel de superview vous donnera le contentView, et enfin le second vous donnera l'UITableViewCell. La deuxième solution ne fonctionne pas bien si vous ajoutez / supprimez des cellules car cela invalidera l'index de ligne. Par conséquent, je suis allé avec la première solution comme indiqué et cela a fonctionné parfaitement.
raidfive
3
Cela sélectionnera de manière fiable la cellule propriétaire du bouton: UIView * view = button; while (! [view isKindOfClass: [UITableViewCell class]]) {view = [view superview]}
Jacob Lyles
1
Il y a un piège lors de l'utilisation: [bouton addTarget: auto-action: @selector (checkButtonTapped :) forControlEvents: UIControlEventTouchUpInside]; parce que addTarget: action: forControlEvents: ajoutera plusieurs cibles et actions dupliquées lorsque vous faites défiler la table, il ne supprimera pas les cibles et actions précédentes, donc la méthode checkButtonTapped: sera appelée plusieurs fois lorsque vous cliquez sur le bouton. Vous feriez mieux de supprimer la cible et l'action avant de les ajouter
Bandw
48

J'ai trouvé que la méthode d'utilisation de la vue d'ensemble de la vue d'ensemble pour obtenir une référence à l'indexPath de la cellule fonctionnait parfaitement. Merci à iphonedevbook.com (macnsmith) pour le texte du lien de conseil

-(void)buttonPressed:(id)sender {
 UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
 NSIndexPath *clickedButtonPath = [self.tableView indexPathForCell:clickedCell];
...

}
Noix de coco
la source
Cocoanut, votre fragment de code m'a orienté dans la bonne direction pour ma propre variation sur ce problème. Merci! Au cas où quelqu'un d'autre en aurait besoin, mon cas particulier était que le bouton se trouvait dans une cellule personnalisée qui était affichée dans le pied de page. J'ajouterai du code ci
logiciel a évolué
Si vous (lecteur Stackoverflow) essayez ceci et que cela ne fonctionne pas pour vous, vérifiez si dans votre implémentation votre UIButton est réellement le petit-enfant de votre UITableViewCell. Dans mon implémentation, mon UIButton était un enfant direct de mon UITableViewCell, donc j'avais besoin de supprimer l'un des "superview" dans le code de Cocoanut, puis cela a fonctionné.
Jon Schneider
29
C'est tellement, très faux et est cassé dans les nouvelles versions du système d'exploitation. Ne marchez pas dans les arbres que vous ne possédez pas.
Kenrik
2
Cela fonctionnait pour moi sous iOS 6, mais est cassé dans iOS 7. Il semble que @KenrikMarch a un point valide!
Jon Schneider
3
dans iOS 7, c'est une étape supplémentaire dans la vue d'ensemble. par exemple [[[expéditeur superview] superview] superView];
CW0007007
43

Voici comment je le fais. Simple et concis:

- (IBAction)buttonTappedAction:(id)sender
{
    CGPoint buttonPosition = [sender convertPoint:CGPointZero
                                           toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    ...
}
Chris Schwerdt
la source
2
Encore plus simple: utilisez à la CGPointZeroplace de CGPointMake(0, 0);-)
Jakob W
Facile à travailler avec. De plus, facile à traduire en Swift 3. Vous êtes le meilleur :)
Francisco Romero
Traduit ceci vers Swift ci-dessous. La solution la plus simple que j'ai pu trouver. Merci Chris!
Rutger Huijsmans
6

A trouvé une bonne solution à ce problème ailleurs, sans déconner avec les balises sur le bouton:

- (void)buttonPressedAction:(id)sender {

    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    CGPoint currentTouchPosition = [touch locationInView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];

    // do stuff with the indexPath...
}
Alpinista
la source
5
Dans cet exemple, il n'est pas clair d'où vous obtenez l'objet «événement».
Nick Ludlam
C'est la solution que j'ai choisie. L'utilisation de balises est imprévisible lors de l'ajout / la suppression de lignes car leurs index changent. Aussi,
raidfive
@NickLudlam: probablement le nom de la méthode n'est pas buttonPressedAction:mais buttonPressedAction:forEvent:.
KPM
5

Que diriez-vous d'envoyer les informations comme NSIndexPathdans leUIButton injection d'exécution aide.

1) Vous avez besoin de runtime à l'importation

2) ajouter une constante statique

3) ajouter NSIndexPath à votre bouton sur l'exécution en utilisant:

(void) setMetaData: (id) cible withObject: (id) newObj

4) appuyez sur le bouton pour obtenir les métadonnées en utilisant:

(id) metaData: (id) cible

Prendre plaisir

    #import <objc/runtime.h>
    static char const * const kMetaDic = "kMetaDic";


    #pragma mark - Getters / Setters

- (id)metaData:(id)target {
    return objc_getAssociatedObject(target, kMetaDic);
}

- (void)setMetaData:(id)target withObject:(id)newObj {
    objc_setAssociatedObject(target, kMetaDic, newObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}



    #On the cell constructor
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    ....
    cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    ....
    [btnSocial addTarget:self
                                   action:@selector(openComments:)
                         forControlEvents:UIControlEventTouchUpInside];

    #add the indexpath here or another object
    [self setMetaData:btnSocial withObject:indexPath];

    ....
    }



    #The action after button been press:

    - (IBAction)openComments:(UIButton*)sender{

        NSIndexPath *indexPath = [self metaData:sender];
        NSLog(@"indexPath: %d", indexPath.row);

        //Reuse your indexpath Now
    }
magno cardona
la source
1
SI la table est réorganisée ou une ligne supprimée, cela ne fonctionnera pas.
Neil
5

Pour faire (@Vladimir) la réponse est Swift:

var buttonPosition = sender.convertPoint(CGPointZero, toView: self.tableView)
var indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)!

Bien que la vérification indexPath != nilme donne le doigt ... "NSIndexPath n'est pas un sous-type de NSString"

Dennis
la source
5

Avec Swift 4.2 et iOS 12, vous pouvez choisir l'un des 5 exemples complets suivants afin de résoudre votre problème.


#1. Utilisation de UIView' convert(_:to:)et UITableView'indexPathForRow(at:)

import UIKit

private class CustomCell: UITableViewCell {

    let button = UIButton(type: .system)

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        button.setTitle("Tap", for: .normal)
        contentView.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
        button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.button.addTarget(self, action: #selector(customCellButtonTapped), for: .touchUpInside)
        return cell
    }

    @objc func customCellButtonTapped(_ sender: UIButton) {
        let point = sender.convert(CGPoint.zero, to: tableView)
        guard let indexPath = tableView.indexPathForRow(at: point) else { return }
        print(indexPath)
    }

}

# 2. Utilisation de UIView' convert(_:to:)et UITableView'indexPathForRow(at:) (alternative)

Ceci est une alternative à l'exemple précédent où nous passons nilau targetparamètre dans addTarget(_:action:for:). De cette façon, si le premier répondant n'implémente pas l'action, elle sera envoyée au prochain répondant de la chaîne de répondeurs jusqu'à ce qu'une implémentation correcte soit trouvée.

import UIKit

private class CustomCell: UITableViewCell {

    let button = UIButton(type: .system)

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        button.setTitle("Tap", for: .normal)
        button.addTarget(nil, action: #selector(TableViewController.customCellButtonTapped), for: .touchUpInside)
        contentView.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
        button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        return cell
    }

    @objc func customCellButtonTapped(_ sender: UIButton) {
        let point = sender.convert(CGPoint.zero, to: tableView)
        guard let indexPath = tableView.indexPathForRow(at: point) else { return }
        print(indexPath)
    }

}

# 3. Utilisation UITableViewdeindexPath(for:) et modèle de délégué

Dans cet exemple, nous définissons le contrôleur de vue comme délégué de la cellule. Lorsque le bouton de la cellule est tapé, il déclenche un appel à la méthode appropriée du délégué.

import UIKit

protocol CustomCellDelegate: AnyObject {
    func customCellButtonTapped(_ customCell: CustomCell)
}

class CustomCell: UITableViewCell {

    let button = UIButton(type: .system)
    weak var delegate: CustomCellDelegate?

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        button.setTitle("Tap", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        contentView.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
        button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @objc func buttonTapped(sender: UIButton) {
        delegate?.customCellButtonTapped(self)
    }

}
import UIKit

class TableViewController: UITableViewController, CustomCellDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

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

    // MARK: - CustomCellDelegate

    func customCellButtonTapped(_ customCell: CustomCell) {
        guard let indexPath = tableView.indexPath(for: customCell) else { return }
        print(indexPath)
    }

}

# 4. En utilisant UITableView« s indexPath(for:)et une fermeture pour la délégation

Ceci est une alternative à l'exemple précédent où nous utilisons une fermeture au lieu d'une déclaration de délégué de protocole pour gérer le tap tap.

import UIKit

class CustomCell: UITableViewCell {

    let button = UIButton(type: .system)
    var buttontappedClosure: ((CustomCell) -> Void)?

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        button.setTitle("Tap", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        contentView.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
        button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @objc func buttonTapped(sender: UIButton) {
        buttontappedClosure?(self)
    }

}
import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.buttontappedClosure = { [weak tableView] cell in
            guard let indexPath = tableView?.indexPath(for: cell) else { return }
            print(indexPath)
        }
        return cell
    }

}

# 5. Utilisation de UITableViewCell' accessoryTypeet UITableViewDelegate'tableView(_:accessoryButtonTappedForRowWith:)

Si le bouton est un UITableViewCell« contrôle accessoire standard s, tout robinet sur elle déclenchera un appel à UITableViewDelegate» s tableView(_:accessoryButtonTappedForRowWith:), vous permettant d'obtenir le chemin d'index connexe.

import UIKit

private class CustomCell: UITableViewCell {

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        accessoryType = .detailButton
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
import UIKit

class TableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        return cell
    }

    override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
        print(indexPath)
    }

}
Imanou Petit
la source
5
func buttonAction(sender:UIButton!)
    {
        var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tablevw)
        let indexPath = self.tablevw.indexPathForRowAtPoint(position)
        let cell: TableViewCell = tablevw.cellForRowAtIndexPath(indexPath!) as TableViewCell
        println(indexPath?.row)
        println("Button tapped")
    }
Ankit Bansal
la source
3

J'utiliserais la propriété de balise comme vous l'avez dit, en définissant la balise comme suit:

[button setTag:indexPath.row];

puis obtenir la balise à l'intérieur du boutonPressedAction comme ceci:

((UIButton *)sender).tag

Ou

UIButton *button = (UIButton *)sender; 
button.tag;
ACBurk
la source
5
Cette approche est complètement rompue pour les tableaux avec sections.
ohhorob
non, vous pouvez également utiliser une fonction simple pour placer la section dans la balise.
ACBurk
2
tagest un entier. semble un peu maladroit d'encoder / décoder les chemins d'index dans les balises de vue.
ohhorob
C'est exact, mais c'est une solution, mais pas celle que j'utiliserais si j'avais des sections. Tout ce que j'essayais de dire, c'est que cela pouvait être fait en utilisant cette méthode, qu'elle n'était pas cassée. Une version meilleure et plus complexe déterminerait le chemin d'index à partir de la position du bouton à l'intérieur de UITableView. Cependant, puisque rein a dit qu'il n'avait que cinq cellules (sans sections), cela rend probablement cette méthode trop compliquée et votre commentaire initial et tout ce fil de commentaires inutile.
ACBurk
3

Bien que j'aime la façon de tag ... si vous ne voulez pas utiliser de tags pour une raison quelconque, vous pouvez créer un membre NSArrayde boutons prédéfinis:

NSArray* buttons ;

puis créez ces boutons avant de rendre la tableView et poussez-les dans le tableau.

Ensuite, à l'intérieur de la tableView:cellForRowAtIndexPath:fonction, vous pouvez faire:

UIButton* button = [buttons objectAtIndex:[indexPath row] ] ;
[cell.contentView addSubview:button];

Ensuite, dans la buttonPressedAction:fonction, vous pouvez faire

- (void)buttonPressedAction:(id)sender {
   UIButton* button = (UIButton*)sender ;
   int row = [buttons indexOfObject:button] ;
   // Do magic
}
Eld
la source
2

POUR GÉRER LES SECTIONS - J'ai stocké le NSIndexPath dans une UITableViewCell personnalisée

IN CLKIndexPricesHEADERTableViewCell.xib

IN IB Ajouter UIButton à XIB - NE PAS ajouter d'action!

Ajouter une sortie @property (conserver, non anatomique) IBOutlet UIButton * buttonIndexSectionClose;

NE PAS CTRL + DRAG une action dans IB (fait dans le code ci-dessous)

@interface CLKIndexPricesHEADERTableViewCell : UITableViewCell
...
@property (retain, nonatomic) IBOutlet UIButton *buttonIndexSectionClose;
@property (nonatomic, retain) NSIndexPath * indexPathForCell;
@end

In viewForHeaderInSection (devrait également fonctionner pour cellForRow .... etc si votre table n'a qu'une seule section)

- viewForHeaderInSection is called for each section 1...2...3
- get the cell CLKIndexPricesHEADERTableViewCell 
- getTableRowHEADER just does the normal dequeueReusableCellWithIdentifier
- STORE the indexPath IN the UITableView cell
- indexPath.section = (NSInteger)section
- indexPath.row = 0 always (we are only interested in sections)

- (UIView *) tableView:(UITableView *)tableView1 viewForHeaderInSection:(NSInteger)section {


    //Standard method for getting a UITableViewCell
    CLKIndexPricesHEADERTableViewCell * cellHEADER = [self getTableRowHEADER];

... utilisez la section pour obtenir des données pour votre cellulaire

...compléter le

   indexName        = ffaIndex.routeCode;
   indexPrice       = ffaIndex.indexValue;

   //

   [cellHEADER.buttonIndexSectionClose addTarget:self
                                          action:@selector(buttonDELETEINDEXPressedAction:forEvent:)
                                forControlEvents:UIControlEventTouchUpInside];


   cellHEADER.indexPathForCell = [NSIndexPath indexPathForRow:0 inSection:section];


    return cellHEADER;
}

L'UTILISATEUR appuie sur le bouton SUPPRIMER sur un en-tête de section et cela appelle

- (void)buttonDELETEINDEXPressedAction:(id)sender forEvent:(UIEvent *)event
{
    NSLog(@"%s", __PRETTY_FUNCTION__);


    UIView *  parent1 = [sender superview];   // UiTableViewCellContentView
    //UIView *myContentView = (UIView *)parent1;

    UIView *  parent2 = [parent1 superview];  // custom cell containing the content view
    //UIView *  parent3 = [parent2 superview];  // UITableView containing the cell
    //UIView *  parent4 = [parent3 superview];  // UIView containing the table


    if([parent2 isMemberOfClass:[CLKIndexPricesHEADERTableViewCell class]]){
        CLKIndexPricesHEADERTableViewCell *myTableCell = (CLKIndexPricesHEADERTableViewCell *)parent2;

        //UITableView *myTable = (UITableView *)parent3;
        //UIView *mainView = (UIView *)parent4;

        NSLog(@"%s indexPath.section,row[%d,%d]", __PRETTY_FUNCTION__, myTableCell.indexPathForCell.section,myTableCell.indexPathForCell.row);

        NSString *key = [self.sortedKeysArray objectAtIndex:myTableCell.indexPathForCell.section];
        if(key){
            NSLog(@"%s DELETE object at key:%@", __PRETTY_FUNCTION__,key);
            self.keyForSectionIndexToDelete = key;
            self.sectionIndexToDelete = myTableCell.indexPathForCell.section;

            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Remove Index"
                                                                message:@"Are you sure"
                                                               delegate:self
                                                      cancelButtonTitle:@"No"
                                                      otherButtonTitles:@"Yes", nil];
            alertView.tag = kALERTVIEW_REMOVE_ONE_INDEX;
            [alertView show];
            [alertView release];
            //------
        }else{
            NSLog(@"ERROR: [%s] key is nil for section:%d", __PRETTY_FUNCTION__,myTableCell.indexPathForCell.section);
        }

    }else{
        NSLog(@"ERROR: [%s] CLKIndexPricesHEADERTableViewCell not found", __PRETTY_FUNCTION__);
    }
}

Dans cet exemple, j'ai ajouté un bouton Supprimer et je devrais donc afficher UIAlertView pour le confirmer

Je stocke la section et la clé dans le dictionnaire stockant des informations sur la section dans un ivar dans le VC

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
   if(alertView.tag == kALERTVIEW_REMOVE_ONE_INDEX){
        if(buttonIndex==0){
            //NO
            NSLog(@"[%s] BUTTON:%d", __PRETTY_FUNCTION__,buttonIndex);
            //do nothing
        }
        else if(buttonIndex==1){
            //YES
            NSLog(@"[%s] BUTTON:%d", __PRETTY_FUNCTION__,buttonIndex);
            if(self.keyForSectionIndexToDelete != nil){

                //Remove the section by key
                [self.indexPricesDictionary removeObjectForKey:self.keyForSectionIndexToDelete];

                //sort the keys so sections appear alphabetically/numbericsearch (minus the one we just removed)
                [self updateTheSortedKeysArray];                

                //Delete the section from the table using animation
                [self.tableView beginUpdates];

                [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:self.sectionIndexToDelete]
                              withRowAnimation:UITableViewRowAnimationAutomatic];
                [self.tableView endUpdates];

                //required to trigger refresh of myTableCell.indexPathForCell else old values in UITableViewCells
                [self.tableView reloadData];
            }else{
                NSLog(@"ERROR: [%s] OBJECT is nil", __PRETTY_FUNCTION__);
            }
        }
        else {
            NSLog(@"ERROR: [%s] UNHANDLED BUTTON:%d", __PRETTY_FUNCTION__,buttonIndex);
        }
    }else {
        NSLog(@"ERROR: [%s] unhandled ALERTVIEW TAG:%d", __PRETTY_FUNCTION__,alertView.tag);
    }
}
brian.clear
la source
2
A better way would be to subclass your button and add a indexPath property to it.

//Implement a subclass for UIButton.

@interface NewButton:UIButton
@property(nonatomic, strong) NSIndexPath *indexPath;


Make your button of type NewButton in the XIB or in the code whereever you are initializing them.

Then in the cellForRowAtIndexPath put the following line of code.

button.indexPath = indexPath;

return cell; //As usual



Now in your IBAction

-(IBAction)buttonClicked:(id)sender{
   NewButton *button = (NewButton *)sender;

//Now access the indexPath by buttons property..

   NSIndexPath *indexPath = button.indexPath; //:)
}
mmmanishs
la source
Ceci est légèrement bogué car l'indexPath d'une cellule peut changer si vous appelez deleteRowsAtIndexPaths.
John Gibb
deleteRowsAtIndexPaths provoquera le rappel de cellForRowAtIndexPath. Les boutons auront alors de nouveaux indexPath corrects.
mmmanishs
1

Cela fonctionne aussi pour moi, merci @Cocoanut

J'ai trouvé que la méthode d'utilisation de la vue d'ensemble de la vue d'ensemble pour obtenir une référence à l'indexPath de la cellule fonctionnait parfaitement. Merci à iphonedevbook.com (macnsmith) pour le texte du lien de conseil

-(void)buttonPressed:(id)sender {
 UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
 NSIndexPath *clickedButtonPath = [self.tableView indexPathForCell:clickedCell];
...

}
user366584
la source
0

vous pouvez utiliser le modèle de balise:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
     NSString *identifier = @"identifier";
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
     if (cell == nil) {
         cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
         [cell autorelelase];

         UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 5, 40, 20)];
         [button addTarget:self action:@selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
         [button setTag:[indexPath row]]; //use the row as the current tag
         [cell.contentView addSubview:button];

         [button release];
     }

     UIButton *button = (UIButton *)[cell viewWithTag:[indexPath row]]; //use [indexPath row]
     [button setTitle:@"Edit" forState:UIControlStateNormal];

     return cell;
}

- (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    //button.tag has the row number (you can convert it to indexPath)
}
Nir Levy
la source
Comment pourrais-je marquer les contrôles si j'avais plusieurs contrôles sur une seule cellule?
rêne
Je ne suis pas sûr que cela fonctionnerait - si la cellule est créée pour la ligne n ° 1, elle obtiendra la balise 1. Si elle est retirée de la file pour la ligne n ° 3, elle aura toujours une balise de 1, pas 3.
rein
suppose que vous avez raison sur le deuxième commentaire. ma faute. Je pense que votre meilleure solution consiste à sous-classer UIButton, à ajouter une ou deux autres propriétés, puis à les définir / obtenir dans les cas appropriés (respectez la balise: 1 que vous aviez dans votre code)
Nir Levy
0

Suis-je en train de manquer quelque chose? Ne pouvez-vous pas simplement utiliser l'expéditeur pour identifier le bouton. L'expéditeur vous donnera des informations comme celle-ci:

<UIButton: 0x4b95c10; frame = (246 26; 30 30); opaque = NO; tag = 104; layer = <CALayer: 0x4b95be0>>

Ensuite, si vous souhaitez modifier les propriétés du bouton, dites l'image d'arrière-plan que vous venez de dire à l'expéditeur:

[sender setBackgroundImage:[UIImage imageNamed:@"new-image.png"] forState:UIControlStateNormal];

Si vous avez besoin de la balise, la méthode ACBurk est très bien.

Michael Morrison
la source
1
Ils recherchent leur "objet" auquel le bouton se rapporte
ohhorob
0
// how do I know which button sent this message?
// processing button press for this row requires an indexPath.

Assez simple en fait:

- (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;
    CGPoint rowButtonCenterInTableView = [[rowButton superview] convertPoint:rowButton.center toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rowButtonCenterInTableView];
    MyTableViewItem *rowItem = [self.itemsArray objectAtIndex:indexPath.row];
    // Now you're good to go.. do what the intention of the button is, but with
    // the context of the "row item" that the button belongs to
    [self performFooWithItem:rowItem];
}

Fonctionne bien pour moi: P

si vous souhaitez ajuster votre configuration d'action cible, vous pouvez inclure le paramètre d'événement dans la méthode, puis utiliser les touches de cet événement pour résoudre les coordonnées de la touche. Les coordonnées doivent encore être résolues dans les limites de la vue tactile, mais cela peut sembler plus facile pour certaines personnes.

ohhorob
la source
0

créer un tableau nsmutable et mettre tous les boutons dans ce tableau usint [array addObject: yourButton];

dans la méthode de pression des boutons

-

 (void)buttonPressedAction:(id)sender
{
    UIButton *button = (UIButton *)sender;

for(int i=0;i<[yourArray count];i++){

if([buton isEqual:[yourArray objectAtIndex:i]]){

//here write wat u need to do

}
}
Rajesh
la source
0

Une légère variation sur la réponse Cocoanuts (qui m'a aidé à résoudre ce problème) lorsque le bouton était dans le pied de page d'un tableau (ce qui vous empêche de trouver la `` cellule cliquée '':

-(IBAction) buttonAction:(id)sender;
{
    id parent1 = [sender superview];   // UiTableViewCellContentView
    id parent2 = [parent1 superview];  // custom cell containing the content view
    id parent3 = [parent2 superview];  // UITableView containing the cell
    id parent4 = [parent3 superview];  // UIView containing the table

    UIView *myContentView = (UIView *)parent1;
    UITableViewCell *myTableCell = (UITableViewCell *)parent2;
    UITableView *myTable = (UITableView *)parent3;
    UIView *mainView = (UIView *)parent4;

    CGRect footerViewRect = myTableCell.frame;
    CGRect rect3 = [myTable convertRect:footerViewRect toView:mainView];    

    [cc doSomethingOnScreenAtY:rect3.origin.y];
}
logiciel évolué
la source
0

J'utilise toujours des tags.

Vous devez sous-classer le UITableviewCellet gérer la pression sur le bouton à partir de là.

Chris
la source
Je ne comprends pas trop comment. La propriété de balise est configurée lors de la création de la cellule - cette cellule est réutilisable pour chaque ligne avec le même identifiant. Cette balise est spécifique au contrôle dans une cellule générique réutilisable. Comment puis-je utiliser cette balise pour différencier des boutons dans des cellules qui ont été créées de manière générique? Pourriez-vous publier du code?
rêne
0

C'est simple; faire une cellule personnalisée et prendre une sortie de bouton

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
         NSString *identifier = @"identifier";
        customCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    cell.yourButton.tag = indexPath.Row;

- (void)buttonPressedAction:(id)sender

changer l'identifiant de la méthode ci-dessus en (UIButton *)

Vous pouvez obtenir la valeur de quel bouton est exploité en faisant sender.tag.

piyush Bageria
la source
0

Sous-classe le bouton pour stocker la valeur requise, peut-être créer un protocole (ControlWithData ou quelque chose). Définissez la valeur lorsque vous ajoutez le bouton à la cellule de vue tabulaire. Lors de votre retouche, vérifiez si l'expéditeur respecte le protocole et extrayez les données. Je stocke normalement une référence à l'objet réel qui est rendu dans la cellule de vue de table.

Jerome Chan Yeow Heong
la source
0

MISE À JOUR SWIFT 2

Voici comment savoir quel bouton a été tapé + envoyer des données à un autre ViewController à partir de ce bouton indexPath.rowcar je suppose que c'est le point pour la plupart!

@IBAction func yourButton(sender: AnyObject) {


     var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
        let indexPath = self.tableView.indexPathForRowAtPoint(position)
        let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath!)! as
        UITableViewCell
        print(indexPath?.row)
        print("Tap tap tap tap")

    }

Pour ceux qui utilisent une classe ViewController et ont ajouté un tableView, j'utilise un ViewController au lieu d'un TableViewController, j'ai donc ajouté manuellement le tableView afin d'y accéder.

Voici le code pour passer des données à un autre VC en appuyant sur ce bouton et en passant la cellule indexPath.row

@IBAction func moreInfo(sender: AnyObject) {

    let yourOtherVC = self.storyboard!.instantiateViewControllerWithIdentifier("yourOtherVC") as! YourOtherVCVIewController



    var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
    let indexPath = self.tableView.indexPathForRowAtPoint(position)
    let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath!)! as
    UITableViewCell
    print(indexPath?.row)
    print("Button tapped")


    yourOtherVC.yourVarName = [self.otherVCVariable[indexPath!.row]]

    self.presentViewController(yourNewVC, animated: true, completion: nil)

}
Lukesivi
la source
0

Notez ici que j'utilise une cellule personnalisée, ce code fonctionne parfaitement pour moi

 @IBAction func call(sender: UIButton)
    {
        var contentView = sender.superview;
        var cell = contentView?.superview as EmployeeListCustomCell
        if (!(cell.isKindOfClass(EmployeeListCustomCell)))
        {
            cell = (contentView?.superview)?.superview as EmployeeListCustomCell
        }

        let phone = cell.lblDescriptionText.text!
        //let phone = detailObject!.mobile!
        let url:NSURL = NSURL(string:"tel://"+phone)!;
        UIApplication.sharedApplication().openURL(url);
    }
Gaurav
la source
0

La solution de Chris Schwerdt mais chez Swift a fonctionné pour moi:

@IBAction func rateButtonTapped(sender: UIButton) {
    let buttonPosition : CGPoint = sender.convertPoint(CGPointZero, toView: self.ratingTableView)
    let indexPath : NSIndexPath = self.ratingTableView.indexPathForRowAtPoint(buttonPosition)!

    print(sender.tag)
    print(indexPath.row)
}
Rutger Huijsmans
la source
0

Ce problème se compose de deux parties:

1) Obtenir le chemin d'index UITableViewCellqui contient presséUIButton

Il y a quelques suggestions comme:

  • Mise à jour de UIButtonla méthode tagin en cellForRowAtIndexPath:utilisant la rowvaleur du chemin d'index . Ce n'est pas une bonne solution car elle nécessite une mise tagà jour continue et ne fonctionne pas avec les vues de table avec plus d'une section.

  • Ajouter une NSIndexPathpropriété à une cellule personnalisée et la mettre à jour au lieu de UIButtonla méthode tagin cellForRowAtIndexPath:. Cela résout le problème de plusieurs sections mais n'est toujours pas bon car il nécessite toujours une mise à jour.

  • Garder une référence faible au parent UITableViewdans la cellule personnalisée lors de sa création et utiliser la indexPathForCell:méthode pour obtenir le chemin d'index. Semble un peu mieux, pas besoin de mettre à jour quoi que ce soit dans la cellForRowAtIndexPath:méthode, mais nécessite toujours de définir une référence faible lorsque la cellule personnalisée est créée.

  • Utilisation de la superViewpropriété de la cellule pour obtenir une référence au parent UITableView. Pas besoin d'ajouter de propriétés à la cellule personnalisée et pas besoin de définir / mettre à jour quoi que ce soit lors de la création / plus tard. Mais la cellule superViewdépend des détails d'implémentation iOS. Il ne peut donc pas être utilisé directement.

Mais cela peut être réalisé en utilisant une simple boucle, car nous sommes sûrs que la cellule en question doit être dans un UITableView:

UIView* view = self;
while (view && ![view isKindOfClass:UITableView.class])
    view = view.superview;
UITableView* parentTableView = (UITableView*)view;

Ainsi, ces suggestions peuvent être combinées en une méthode de cellule personnalisée simple et sûre pour obtenir le chemin d'index:

- (NSIndexPath *)indexPath
{
    UIView* view = self;

    while (view && ![view isKindOfClass:UITableView.class])
        view = view.superview;

    return [(UITableView*)view indexPathForCell:self];
}

Désormais, cette méthode peut être utilisée pour détecter lequel UIButtonest pressé.

2) Informer les autres parties de l'événement de presse de bouton

Après avoir su en interne qui UIButtonest pressé dans quelle cellule personnalisée avec un chemin d'index exact, ces informations doivent être envoyées à d'autres parties (probablement le contrôleur de vue qui gère le UITableView). Ainsi, cet événement de clic de bouton peut être géré dans un niveau d'abstraction et de logique similaire à la didSelectRowAtIndexPath:méthode du délégué UITableView.

Deux approches peuvent être utilisées pour cela:

a) Délégation: une cellule personnalisée peut avoir une delegatepropriété et peut définir un protocole. Lorsque le bouton est enfoncé, il exécute simplement ses méthodes déléguées sur sa delegatepropriété. Mais cette delegatepropriété doit être définie pour chaque cellule personnalisée lors de leur création. Comme alternative, la cellule personnalisée peut également choisir d'exécuter ses méthodes de délégué sur sa vue de table parent delegate.

b) Centre de notifications: les cellules personnalisées peuvent définir un nom de notification personnalisé et publier cette notification avec le chemin d'index et les informations de vue de la table parent fournies dans l' userInfoobjet. Pas besoin de définir quoi que ce soit pour chaque cellule, il suffit d'ajouter un observateur pour la notification de la cellule personnalisée.

erkanyildiz
la source
0

J'utilise une solution de cette sous-classe UIButtonet j'ai pensé que je devrais simplement la partager ici, les codes dans Swift:

class ButtonWithIndexPath : UIButton {
    var indexPath:IndexPath?
}

N'oubliez pas de mettre à jour son indexPath dans cellForRow(at:)

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let returnCell = tableView.dequeueReusableCell(withIdentifier: "cellWithButton", for: indexPath) as! cellWithButton
    ...
    returnCell.button.indexPath = IndexPath
    returnCell.button.addTarget(self, action:#selector(cellButtonPressed(_:)), for: .touchUpInside)

    return returnCell
}

Ainsi, lorsque vous répondez à l'événement du bouton, vous pouvez l'utiliser comme

func cellButtonPressed(_ sender:UIButton) {
    if sender is ButtonWithIndexPath {
        let button = sender as! ButtonWithIndexPath
        print(button.indexPath)
    }
}
Ben Ong
la source