Fai clic sul pulsante Ottieni all'interno di UITableViewCell


140

Ho un controller di visualizzazione con una vista tabella e un pennino separato per il modello di cella della tabella. Il modello di cella ha alcuni pulsanti. Voglio accedere al clic sul pulsante insieme all'indice della cella su cui è stato fatto clic all'interno del controller della vista in cui ho definito la vista Tabella.

Quindi ho ViewController.he ViewController.mdove ho il UITableViewe TableTemplate.h, TableTemplate.me TableTemplate.xibdove ho definito il pennino. Voglio l'evento del clic sul pulsante con l'indice di cella in ViewController.m.

Qualche aiuto su come posso farlo?

Risposte:


258

1) Nel tuo cellForRowAtIndexPath:metodo, assegna il tag del pulsante come indice:

cell.yourbutton.tag = indexPath.row;

2) Aggiungi target e azione per il tuo pulsante come di seguito:

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

3) Azioni di codice basate sull'indice come di seguito in ViewControler:

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

Aggiornamenti per più sezioni:

È possibile controllare questo collegamento per rilevare il clic del pulsante nella vista tabella per più righe e sezioni.


1
Questo può essere fatto anche tramite Interface Builder (IB) nel passaggio due. Assicurati solo che il tag dei pulsanti sia impostato. Non vuoi davvero confondere la tua azione. Fallo tramite IB o esplicitamente nel tuo codice.
Sententia,

@Mani Non rompe MVC - l'azione è nel TableView non nella cella.
davecom,

@davecom Se si imposta target pulsante come cella (tramite IB), come verrà attivato da tableView? O è il loro modo di collegare il pulsante target a tableview che si trova nel xib della cella?
Mani,

24
Questa soluzione presenta problemi quando si avvia l'inserimento e l'eliminazione di righe. Il tag non viene aggiornato quando le righe vengono spostate. Invece di mantenere un riferimento alla riga. Potrebbe essere meglio mantenere un riferimento a un ID oggetto univoco.
Vincent Cheong,

1
Ogni volta che ti ritrovi ad assegnare valori agli attributi di tag delle viste, hai un pessimo odore di codice che ti può mordere in seguito. Cerca modi migliori per raggiungere il tuo obiettivo, non il primo post SO che trovi.
TigerCoding

148

I delegati sono la strada da percorrere.

Come visto con altre risposte, l'utilizzo delle viste potrebbe non essere aggiornato. Chissà domani potrebbe esserci un altro wrapper e potrebbe essere necessario utilizzarlo cell superview]superview]superview]superview]. E se usi i tag, finiresti con n numero di if if condizioni per identificare la cella. Per evitare tutto ciò, impostare delegati. (In questo modo creerai una classe di celle riutilizzabile. Puoi usare la stessa classe di celle di una classe di base e tutto ciò che devi fare è implementare i metodi delegati.)

Per prima cosa abbiamo bisogno di un'interfaccia (protocollo) che verrà utilizzata dalla cella per comunicare (delegare) i clic sui pulsanti. ( È possibile creare un file .h separato per il protocollo e includerlo sia nel controller della vista tabella sia nelle classi di celle personalizzate O semplicemente aggiungerlo nella classe di celle personalizzata che verrà comunque inclusa nel controller di vista tabella )

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

Includere questo protocollo nel controller di visualizzazione cella e tabella personalizzato. E assicurati che il controller della vista tabella confermi questo protocollo.

Nella cella personalizzata creare due proprietà:

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

Nel UIButtonclic del delegato IBAction: ( Lo stesso può essere fatto per qualsiasi azione nella classe di celle personalizzata che deve essere delegata per visualizzare il controller )

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

Nel controller della vista tabella cellForRowAtIndexPathdopo aver rimosso la cella dalla cella, impostare le proprietà sopra.

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

E implementare il delegato nel controller di visualizzazione tabella:

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

Questo sarebbe l'approccio ideale per ottenere azioni personalizzate dei pulsanti delle celle nel controller della vista tabella.


2
Perché hai reso il delegato una forte proprietà della cella? Questo ti darà un ciclo di mantenimento, a meno che tu non sappia che il controller trattiene debolmente la cella.
JulianSymes,

che dire di _cellIndex beign aggiornato dopo l'eliminazione della cella?
skornos,

2
Ho sentito da un mio amico dire che l'uso del delegato su ogni cella provoca consumo di memoria, quindi usa i tag. È vero?
Bista,

2
controlla questa domanda amico stackoverflow.com/questions/31649220/…
Nischal Hada,

@the_UB Non ci può essere molto tra l'impostazione di un tag e la memorizzazione di un singolo riferimento. Forse un tag richiederebbe più memoria.
Ian Warburton,

66

Invece di giocare con i tag, ho adottato un approccio diverso. Fatto delegato per la mia sottoclasse di UITableViewCell (OptionButtonsCell) e aggiunto un indexPath var. Dal mio pulsante nello storyboard ho collegato @IBAction a OptionButtonsCell e lì ho inviato il metodo delegato con il giusto indexPath a chiunque fosse interessato. Nella cella per il percorso dell'indice ho impostato indexPath corrente e funziona :)

Lascia che il codice parli da solo:

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)")
}

mi potete aiutare, sto ricevendo un errore in questa riga: class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate // errore: conformità ridondante di "MyTableViewController" al protocollo "UITableViewDataSource"
Ulug'bek Ro'zimboyev

sembra che tu stia cercando di conformarti a UITableViewDataSource più volte. Forse hai un'estensione in cui sei già conforme all'origine dati? Non puoi fare di più senza codice
Maciej Chrzastek,

1
e come passare i dati per eseguire segue e passare a un altro controller di visualizzazione?
Milad Faridnia,

2
La soluzione migliore e più pulita!
Appsunited

31

Questo dovrebbe aiutare: -

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

Qui mittente è l'istanza di UIButton che sta inviando l'evento. myTableView è l'istanza di UITableView con cui hai a che fare.

Basta avere il riferimento di cella giusto e tutto il lavoro è fatto.

Potrebbe essere necessario rimuovere i pulsanti dal contentView della cella e aggiungerli direttamente all'istanza UITableViewCell mentre è una subview.

O

È possibile formulare uno schema di denominazione dei tag per UIButtons diversi in cell.contentView. Usando questo tag, in seguito puoi conoscere le informazioni sulla riga e sulla sezione secondo necessità.


4
dovrebbe essere [[superview mittente] superview];
pierre23,

2
Questo è buono per le celle molto semplici. Tuttavia, se la tua cella ha un profondo albero di vedute, la risposta di Mani è la migliore.
Sententia,

3
Ora in iOS 7 dovrebbe essere UITableViewCell * cell = (UITableViewCell *) [[[sender superview] superview] superview]; Grazie.
Rajan Maharjan


22

Il seguente codice potrebbe aiutarti.

Ho preso UITableViewcon prototipo di classe di cella personalizzato chiamato UITableViewCellall'interno UIViewController.

Quindi ho ViewController.h, ViewController.me TableViewCell.h,TableViewCell.m

Ecco il codice per questo:

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 cellulare personalizzata:

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

e

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);
    }
}

Nota : qui ho preso tutto UIControlsusando Storyboard.

Spero che ti possa aiutare ... !!!


Il modo migliore di sempre
Daniel Raouf,

15

Il motivo che mi piace sotto la tecnica perché mi aiuta anche a identificare la sezione della tabella.

Aggiungi pulsante nella cella 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 evento:

-(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;
    }
}

Spero che questo aiuto.



12

Usa chiusure 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

Il codice di Tarun non funziona su iOS7, poiché la struttura UITableViewCell è cambiata e ora otterrebbe invece "UITableViewCellScrollView".

Questo post Ottenere UITableViewCell con superview in iOS 7 ha una buona soluzione per creare un loop per trovare la vista padre corretta, indipendentemente da eventuali cambiamenti futuri nella struttura. Si riduce alla creazione di un ciclo:

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

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

Il collegamento ha codice per una soluzione più riutilizzabile, ma dovrebbe funzionare.


6

Swift 2.2

Devi aggiungere un target per quel pulsante.

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

FunctionName: connesso // per esempio

E ovviamente devi impostare il tag di quel pulsante poiché lo stai usando.

myButton.tag = indexPath.row

È possibile ottenere questo da sottoclasse UITableViewCell. Usalo nel generatore di interfaccia, rilascia un pulsante su quella cella, collegalo tramite presa e il gioco è fatto.

Per ottenere il tag nella funzione connessa:

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

6

Swift 3 con una chiusura

Una buona soluzione consiste nell'utilizzare una chiusura in un UITableViewCell personalizzato per richiamare il viewController per un'azione.

Nella cella:

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()
    }
}

In View Controller: delegato 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

Trovo più semplice sottoclassare il pulsante all'interno della tua cella (Swift 3):

class MyCellInfoButton: UIButton {
    var indexPath: IndexPath?
}

Nella tua classe cellulare:

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

Nell'origine dati della vista tabella o della vista raccolta, quando si esegue il dequeueing della cella, assegnare al pulsante il percorso dell'indice:

cell.infoButton.indexPath = indexPath

Quindi puoi semplicemente inserire questo codice nel controller della vista tabella:

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

E non dimenticare di impostare la classe del pulsante nel tuo Interface Builder e collegarlo alla handleTapOnCellInfoButtonfunzione!


modificato:

Utilizzo dell'iniezione di dipendenza. Per impostare chiamando una chiusura:

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

e iniettare la chiusura nel metodo willDisplay del delegato della vista raccolta:

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

L'approccio di chiusura è il modo più Swifty che ho visto per fare questo. Bel lavoro!
Clifton Labrum,

5

Per me funziona.

 - (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

per 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

Se si desidera passare il valore del parametro dalla cella a UIViewController utilizzando la chiusura, quindi

//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 risposta di @Mani è buona, tuttavia i tag delle viste all'interno del contenuto della cella Visualizza spesso vengono utilizzati per altri scopi. Puoi invece usare il tag cell (o il tag contentView della cella):

1) Nel tuo cellForRowAtIndexPath:metodo, assegna il tag della cella come indice:

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

2) Aggiungi target e azione per il tuo pulsante come di seguito:

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

3) Crea un metodo che restituisca la riga del mittente (grazie a @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) Azioni di codice basate sull'indice:

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

0

CustomTableCell.h è un UITableViewCell:

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

MyVC.m dopo le importazioni:

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

All'interno di "cellForRowAtIndexPath" in 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];

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.