Clique no botão Obter dentro do UITableViewCell

140

Eu tenho um controlador de exibição com uma exibição de tabela e uma ponta separada para o modelo de célula da tabela. O modelo de célula possui alguns botões. Desejo acessar o botão clicar junto com o índice da célula clicada dentro do controlador de exibição em que defini a exibição de Tabela.

Então eu tenho ViewController.he ViewController.monde eu tenho o UITableViewe TableTemplate.h, TableTemplate.me TableTemplate.xibonde eu tenho a ponta definida. Quero o evento de clique no botão com o índice de células ViewController.m.

Alguma ajuda sobre como posso fazer isso?

ankit_rck
fonte

Respostas:

258

1) No seu cellForRowAtIndexPath:método, atribua a tag button como índice:

cell.yourbutton.tag = indexPath.row;

2) Adicione alvo e ação ao seu botão, como abaixo:

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

3) Codifique ações com base no índice, conforme abaixo, em ViewControler:

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

Atualizações para várias seções:

Você pode verificar este link para detectar o clique do botão na visualização da tabela para várias linhas e seções.

Mani
fonte
1
Isso também pode ser feito através do Interface Builder (IB) na etapa dois. Apenas verifique se a etiqueta de botões está definida. Você realmente não deseja misturar suas chamadas de ação. Faça isso através do IB ou explicitamente no seu código.
Sententia 29/04
@Ani Não quebra o MVC - a ação está no TableView e não no Cell.
Davecom 31/05
@davecom Se você definir o destino do botão como célula (via IB), como será acionado a partir do tableView? Ou existe alguma maneira de conectar o botão alvo ao tableview, colocado no xib da célula?
Mani
24
Esta solução apresenta problemas quando você começa a inserir e excluir linhas. A tag não é atualizada quando as linhas são deslocadas. Em vez de manter uma referência à linha. Talvez seja melhor manter uma referência a um ID de objeto exclusivo.
Vincent Cheong
1
Sempre que você atribui valores aos atributos de tag das visualizações, você tem um cheiro muito ruim de código que pode morder você mais tarde. Procure maneiras melhores de atingir seu objetivo, não a primeira postagem de SO que você encontrar.
TigerCoding
148

Os delegados são o caminho a percorrer.

Como visto em outras respostas, o uso de visualizações pode ficar desatualizado. Quem sabe amanhã pode haver outro invólucro e talvez precise usar cell superview]superview]superview]superview]. E se você usar tags, você terá um número n de condições senão para identificar a célula. Para evitar tudo isso, configure os delegados. (Ao fazer isso, você criará uma classe de célula reutilizável. Você pode usar a mesma classe de célula que uma classe base e tudo que você precisa fazer é implementar os métodos de delegação.)

Primeiro, precisamos de uma interface (protocolo) que será usada pela célula para comunicar (delegar) cliques no botão. ( Você pode criar um arquivo .h separado para o protocolo e incluir no controlador de exibição de tabela e nas classes de células personalizadas OU basta adicioná-lo na classe de célula personalizada, que será incluída no controlador de exibição de tabela )

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

Inclua esse protocolo no controlador de exibição de célula e tabela personalizado. E verifique se o controlador de exibição de tabela confirma este protocolo.

Na célula personalizada, crie duas propriedades:

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

No UIButtondelegado IBAction, clique em: (O mesmo pode ser feito para qualquer ação na classe de célula personalizada que precise ser delegada novamente para visualizar o controlador )

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

No controlador de exibição de tabela cellForRowAtIndexPathapós remover a fila da célula, defina as propriedades acima.

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

E implemente o delegado no controlador de exibição de tabela:

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

Essa seria a abordagem ideal para obter ações personalizadas de botão de célula no controlador de exibição de tabela.

GoodSp33d
fonte
2
Por que você fez do delegado uma forte propriedade da célula? Isso fornecerá um ciclo de retenção, a menos que você saiba que o controlador apenas retém a célula de maneira fraca.
perfil completo de JulianSymes
e o beign _cellIndex atualizado após a célula ser excluída?
Skores # 7/15
2
Ouvi um amigo dizer que usar delegar em cada célula causa consumo de memória; portanto, use tags. Isso é verdade?
Bista
2
verifique esta questão cara stackoverflow.com/questions/31649220/...
Nischal Hada
@the_UB Não pode haver muito tempo entre definir uma tag e armazenar uma única referência. Possivelmente, uma etiqueta ocuparia mais memória.
Ian Warburton
66

Em vez de brincar com tags, adotei uma abordagem diferente. Fiz delegado para minha subclasse de UITableViewCell (OptionButtonsCell) e adicionei um indexPath var. No meu botão no storyboard, conectei o @IBAction ao OptionButtonsCell e enviei o método delegate com o indexPath correto para qualquer pessoa interessada. Na célula para o caminho do índice, defino o indexPath atual e ele funciona :)

Deixe o código falar por si:

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)")
}
Maciej Chrzastek
fonte
você pode me ajudar, estou recebendo erro nesta linha: class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate // erro: conformidade redundante de 'MyTableViewController' ao protocolo 'UITableViewDataSource'
Ulug'bek Ro'zimboyev
parece que você está tentando se conformar ao UITableViewDataSource várias vezes. Talvez você tem uma extensão onde já estão em conformidade com fonte de dados ?, não pode ajudar mais, sem código
Maciej Chrzastek
1
e como passar dados para executar segue e ir para outro controlador de exibição?
Milad Faridnia
2
Melhor e mais limpa solução!
appsunited
31

Isso deve ajudar: -

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

Aqui remetente é a instância UIButton que está enviando o evento. myTableView é a instância UITableView com a qual você está lidando.

Basta acertar a referência da célula e todo o trabalho é feito.

Pode ser necessário remover os botões do contentView da célula e adicioná-los diretamente à instância UITableViewCell, pois é uma subview.

Ou

Você pode formular um esquema de nomeação de tags para diferentes UIButtons em cell.contentView. Usando essa tag, mais tarde você poderá conhecer as informações de linha e seção conforme necessário.

Tarun
fonte
4
deve ser [[superview do remetente] superview];
precisa
2
Isso é bom para células muito simples. No entanto, se o seu celular tiver uma árvore de pontos de vista profunda, a resposta de Mani é a melhor.
Sententia 29/04
3
Agora, no iOS 7, ele deve ser UITableViewCell * cell = (UITableViewCell *) [[[remetente super visão] super visão] super visão]; Obrigado.
Rajan Maharjan
verifique esta questão cara stackoverflow.com/questions/31649220/...
Nischal Hada
22

O código a seguir pode ajudá-lo.

Tomei UITableViewcom classe de célula protótipo personalizado chamado UITableViewCelldentroUIViewController .

Então eu tenho ViewController.h, ViewController.me TableViewCell.h,TableViewCell.m

Aqui está o código para isso:

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 célula personalizada:

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 : Aqui eu peguei todos UIControlsusando o Storyboard.

Espero que isso te ajude...!!!

Piyush
fonte
A melhor maneira de sempre
Daniel Raouf
15

A razão pela qual eu gosto abaixo da técnica, porque também me ajuda a identificar a seção da tabela.

Adicionar botão na célula 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];

Evento addTask:

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

Espera esta ajuda.

Yogesh Lolusare
fonte
verifique esta questão cara stackoverflow.com/questions/31649220/...
Nischal Hada
12

Use os fechos 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
    }
}
valexa
fonte
7

O código de Tarun não funciona no iOS7, pois a estrutura UITableViewCell mudou e agora ele receberia "UITableViewCellScrollView".

Esta postagem Obtendo o UITableViewCell com superview no iOS 7 tem uma boa solução, criando um loop para encontrar a exibição pai correta, independentemente de quaisquer alterações futuras na estrutura. Tudo se resume a criar um loop:

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

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

O link possui código para uma solução mais reutilizável, mas isso deve funcionar.

Stenio Ferreira
fonte
6

Swift 2.2

Você precisa adicionar um destino para esse botão.

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

Nome da Função: conectado // por exemplo

E é claro que você precisa definir a tag desse botão, pois está usando.

myButton.tag = indexPath.row

Você pode conseguir isso subclassificando UITableViewCell. Use-o no construtor de interfaces, solte um botão nessa célula, conecte-o via tomada e pronto.

Para obter a tag na função conectada:

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

Swift 3 com um fechamento

Uma boa solução é usar um fechamento em um UITableViewCell personalizado para retornar o retorno ao viewController para uma ação.

Na célula:

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: Delegado do 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 
    }
}
Sevy11
fonte
5

Acho mais simples subclassificar o botão dentro do seu celular (Swift 3):

class MyCellInfoButton: UIButton {
    var indexPath: IndexPath?
}

Na sua classe de célula:

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

Na fonte de dados da visualização de tabela ou de coleta, ao remover a fila da célula, forneça ao botão seu caminho de índice:

cell.infoButton.indexPath = indexPath

Portanto, você pode simplesmente colocar esses códigos no seu controlador de exibição de tabela:

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

E não se esqueça de definir a classe do botão no seu Interface Builder e vinculá-lo à handleTapOnCellInfoButtonfunção!


editado:

Usando injeção de dependência. Para configurar a chamada de fechamento:

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

e injete o fechamento no método willDisplay do delegado da exibição da coleção:

 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    (cell as? MyCell)?.someFunction = {
        print(indexPath) // Do something with the indexPath.
    }
}
yesleon
fonte
A abordagem de fechamento é a maneira mais rápida que eu já vi para fazer isso. Bom trabalho!
Clifton Labrum
5

Seu trabalho para mim.

 - (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];
}
Yogesh Tarsariya
fonte
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




}
Hitesh Chauhan
fonte
1

para swift 4:

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

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

Radhe Yadav
fonte
1

Se você deseja passar o valor do parâmetro da célula para o UIViewController usando o encerramento,

//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
    }
}
prachit
fonte
0

A resposta do @Mani é boa, no entanto, as tags de visualizações no conteúdo da célula geralmente são usadas para outros fins. Você pode usar a tag da célula (ou a tag contentView da célula):

1) No seu cellForRowAtIndexPath:método, atribua a tag da célula como índice:

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

2) Adicione alvo e ação ao seu botão, como abaixo:

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

3) Crie o método que retorna a linha do remetente (obrigado @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) Codifique ações com base no índice:

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

CustomTableCell.h é um UITableViewCell:

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

MyVC.m após importações:

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

Dentro de "cellForRowAtIndexPath" em 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];
}
Mer
fonte
-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];

}
user8132169
fonte