Detectando qual UIButton foi pressionado em um UITableView

212

Eu tenho um UITableViewcom 5 UITableViewCells. Cada célula contém um UIButtonque é configurado da seguinte maneira:

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

Minha pergunta é a seguinte: no buttonPressedAction:método, como sei qual botão foi pressionado. Eu considerei usar tags, mas não tenho certeza se esse é o melhor caminho. Eu gostaria de poder, de alguma forma, marcar indexPatho comando no controle.

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

Qual é a maneira padrão de fazer isso?

Editar:

Eu meio que resolvi fazendo o seguinte. Eu ainda gostaria de ter uma opinião se essa é a maneira padrão de fazê-lo ou se existe uma maneira melhor?

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

O que é importante observar é que não posso definir a marca na criação da célula, pois ela pode ser desenfileirada. Parece muito sujo. Deve haver uma maneira melhor.

rédea
fonte
Não vejo nenhum problema ao usar sua solução de tags. As células são reutilizadas, portanto, faz sentido definir a tag para o índice de linha da maneira que você está fazendo aqui. Acho isso uma solução muito mais elegante do que converter o local do toque em um índice de linha, conforme sugerido abaixo.
Erik van der Neut

Respostas:

400

Na amostra de acessórios da Apple, o seguinte método é usado:

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

Em seguida, no manipulador de toque, coordenada de toque recuperada e o caminho do índice é calculado a partir dessa coordenada:

- (void)checkButtonTapped:(id)sender
{
    CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     ...
    }
}
Vladimir
fonte
Sim, é nisso que eu decidi (veja minha edição). Eu concordo com você que não é o ideal.
rédea
2
Mas você também adiciona UIButton ao UITableViewCell, para que seja consistente com o que faz ao criar a célula. Embora esta abordagem não realmente olhar elegante Eu tenho que admitir
Vladimir
1
Para a primeira solução, você precisará pegar [[button superview] superview], pois a primeira chamada de superview fornecerá o contentView e, finalmente, a segunda, o UITableViewCell. A segunda solução não funcionará bem se você estiver adicionando / removendo células, uma vez que invalidará o índice de linha. Portanto, eu fui com a primeira solução descrita e ela funcionou perfeitamente.
raidfive 21/05
3
Isso selecionará com segurança a célula que possui o botão: UIView * view = button; while (! [view isKindOfClass: [UITableViewCell class]]]) {view = [view superview]}
Jacob Lyles #
1
Existe uma armadilha ao usar: [botão addTarget: ação própria: @selector (checkButtonTapped :) forControlEvents: UIControlEventTouchUpInside]; como addTarget: action: forControlEvents: adicionará vários destinos e ações duplicados ao rolar a tabela, ele não removerá os destinos e ações anteriores; portanto, o método checkButtonTapped: será chamado várias vezes quando você clicar no botão. É melhor remover o destino e ação antes de adicioná-los
bandw
48

Eu encontrei o método de usar a superview da superview para obter uma referência ao indexPath da célula funcionou perfeitamente. Agradecimentos a iphonedevbook.com (macnsmith) pelo texto do link da dica

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

}
Cocoanut
fonte
Cocoanut, seu fragmento de código me apontou na direção certa para minha própria variação sobre esse problema. Obrigado! Caso alguém mais precise, meu caso especial foi o de que o botão estava em uma célula personalizada que estava sendo exibida como parte do rodapé. Vou adicionar o código abaixo
software evoluiu
Se você (leitor do Stackoverflow) tentar isso e não funcionar, verifique se na sua implementação o seu UIButton é realmente o neto do seu UITableViewCell. Na minha implementação, meu UIButton era filho direto do meu UITableViewCell, então eu precisei remover uma das "supervisões" no código do Cocoanut e funcionou.
Jon Schneider
29
Isso é muito, muito errado e está quebrado nas versões mais recentes do sistema operacional. Não ande com árvores de super visão que você não possui.
Kenrik março
2
Isso funcionava para mim no iOS 6, mas está quebrado no iOS 7. Parece que o @KenrikMarch tem um ponto válido!
Jon Schneider
3
no iOS 7, é mais um passo na superview. por exemplo [[[remetente super visão] super visão] superView];
CW0007007 21/01
43

Aqui está como eu faço isso. Simples e conciso:

- (IBAction)buttonTappedAction:(id)sender
{
    CGPoint buttonPosition = [sender convertPoint:CGPointZero
                                           toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    ...
}
Chris Schwerdt
fonte
2
Ainda mais simples: use CGPointZeroem vez de CGPointMake(0, 0);-)
Jakob W
Fácil de trabalhar. Além disso, fácil de traduzir para o Swift 3. Você é o melhor :)
Francisco Romero
Traduzido para Swift abaixo. Solução mais fácil que encontrei. Obrigado Chris!
Rutger Huijsmans
6

Encontrei uma boa solução para esse problema em outro lugar, sem mexer nas tags no botão:

- (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
fonte
5
Não está claro neste exemplo de onde você obtém o objeto 'event'.
quer
Esta é a solução que eu usei. O uso de tags é imprevisível ao adicionar / remover linhas, pois seus índices são alterados. Além disso,
raidfive
@ NickLudlam: provavelmente o nome do método não é buttonPressedAction:mas buttonPressedAction:forEvent:.
KPM
5

Que tal enviar informações como NSIndexPathna UIButtoninjeção de tempo de execução.

1) Você precisa de tempo de execução na importação

2) adicione constante estática

3) adicione NSIndexPathao seu botão no tempo de execução usando:

(void) setMetaData: (id) destino comObject: (id) newObj

4) ao pressionar o botão obter metadados usando:

(id) metaData: (id) destino

Aproveitar

    #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
fonte
1
Se a tabela for reorganizada ou uma linha excluída, isso não funcionará.
21413 Neil
5

To do (@Vladimir), a resposta é Swift:

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

Embora a verificação indexPath != nilme dê o dedo ... "NSIndexPath não é um subtipo de NSString"

dennis
fonte
5

Com o Swift 4.2 e iOS 12, você pode escolher um dos 5 exemplos completos a seguir para resolver seu problema.


# 1 Usando UIView's convert(_:to:)e UITableView' sindexPathForRow(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 Usando UIView's convert(_:to:)e UITableView' s indexPathForRow(at:)(alternativa)

Esta é uma alternativa ao exemplo anterior, onde passamos nilao targetparâmetro no addTarget(_:action:for:). Dessa forma, se o primeiro respondedor não implementar a ação, ele será enviado para o próximo respondente na cadeia de respondentes até que uma implementação adequada seja encontrada.

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 Usando UITableView's indexPath(for:)e padrão de delegado

Neste exemplo, definimos o controlador de exibição como o delegado da célula. Quando o botão da célula é tocado, ele aciona uma chamada para o método apropriado do delegado.

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 Usando UITableView's indexPath(for:)e um fecho para delegação

Esta é uma alternativa ao exemplo anterior, em que usamos um fechamento em vez de uma declaração de delegado de protocolo para manipular o toque no botão.

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 Usando UITableViewCell's accessoryTypee UITableViewDelegate' stableView(_:accessoryButtonTappedForRowWith:)

Se o seu botão é um UITableViewCell's controle acessório padrão, qualquer toque nele irá desencadear uma chamada para UITableViewDelegate' s tableView(_:accessoryButtonTappedForRowWith:), o que lhe permite obter o caminho índice relacionado.

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
fonte
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
fonte
3

Eu usaria a propriedade tag como você disse, definindo a tag da seguinte maneira:

[button setTag:indexPath.row];

e colocar a tag dentro do buttonPressedAction da seguinte maneira:

((UIButton *)sender).tag

Ou

UIButton *button = (UIButton *)sender; 
button.tag;
ACBurk
fonte
5
Essa abordagem é completamente quebrada para tabelas com seções.
ohhorob 14/05
não, você pode usar apenas uma função simples para colocar a seção na tag também.
ACBurk 14/05
2
tagé um número inteiro. parece um pouco desajeitado codificar / decodificar os caminhos do índice nas tags de exibição.
ohhorob
Está correto, mas é uma solução, embora não seja uma que eu usaria se tivesse seções. Tudo o que eu estava tentando dizer era que isso poderia ser feito usando esse método, que não estava quebrado. Uma versão melhor e mais complexa determinaria o caminho do índice a partir da posição do botão dentro do UITableView. No entanto, como a rédea disse que ele tem apenas cinco células (sem seções), provavelmente torna esse método mais complicado e seu comentário inicial e todo esse tópico de comentário são inúteis.
ACBurk
3

Embora eu goste da maneira como as tags ... se você não quiser usar tags por qualquer motivo, poderá criar um membro NSArrayde botões pré-fabricados:

NSArray* buttons ;

crie esses botões antes de renderizar o tableView e envie-os para a matriz.

Então, dentro da tableView:cellForRowAtIndexPath:função, você pode fazer:

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

Então, na buttonPressedAction:função, você pode fazer

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

PARA MANUSEAR SEÇÕES - Eu armazenei o NSIndexPath em um UITableViewCell personalizado

IN CLKIndexPricesHEADERTableViewCell.xib

IN IB Adicione UIButton ao XIB - NÃO adicione ação!

Adicione a saída @ propriedade (reter, não atômica) IBOutlet UIButton * buttonIndexSectionClose;

NÃO CTRL + DRAG uma ação no IB (feita no código abaixo)

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

Em viewForHeaderInSection (também deve funcionar para cellForRow .... etc, se a tabela tiver apenas 1 seção)

- 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];

... use a seção para obter dados para o seu celular

... preencha

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

USER pressiona o botão DELETE no cabeçalho da seção e isso chama

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

Neste exemplo, adicionei um botão Excluir para exibir o UIAlertView para confirmar

Eu guardo a seção e digito no dicionário o armazenamento de informações sobre a seção em um ivar no 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
fonte
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
fonte
Isso é um pouco problemático porque o indexPath de uma célula pode mudar, se você chamar deleteRowsAtIndexPaths.
John Gibb
deleteRowsAtIndexPaths fará com que cellForRowAtIndexPath seja chamado novamente. Em seguida, os botões terão novos indexPaths corretos.
mmmanishs
1

Funciona para mim também, obrigado @Cocoanut

Eu encontrei o método de usar a superview da superview para obter uma referência ao indexPath da célula funcionou perfeitamente. Agradecimentos a iphonedevbook.com (macnsmith) pelo texto do link da dica

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

}
user366584
fonte
0

você pode usar o padrão de tag:

- (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
fonte
Como eu marcaria os controles se tivesse vários controles em uma única célula?
rédea
Não tenho certeza se isso funcionaria - se a célula for criada para a linha # 1, ela receberá a tag 1. Se for retirada da fila para a linha # 3, ainda terá uma marca de 1, não 3.
rein
acho que você está certo sobre o segundo comentário. foi mal. Eu acho que a melhor solução é a subclasse UIButton, adicionar outra propriedade ou dois de seu próprio país, em seguida, definir / obtê-los nos casos apropriados (stick com a tag: 1 você tinha em seu código)
Nir Levy
0

Estou esquecendo de algo? Você não pode simplesmente usar o remetente para identificar o botão. O remetente fornecerá informações como esta:

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

Então, se você quiser alterar as propriedades do botão, diga a imagem de plano de fundo que acabou de dizer ao remetente:

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

Se você precisar da tag, o método do ACBurk está correto.

Michael Morrison
fonte
1
Eles estão procurando seu "objeto" ao qual o botão se relaciona
ohhorob
0
// how do I know which button sent this message?
// processing button press for this row requires an indexPath.

Bem simples, na verdade:

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

Trabalhando bem para mim: P

se você quiser ajustar sua configuração de ação de destino, poderá incluir o parâmetro de evento no método e, em seguida, usar os toques desse evento para resolver as coordenadas do toque. As coordenadas ainda precisam ser resolvidas nos limites da visualização por toque, mas isso pode parecer mais fácil para algumas pessoas.

ohhorob
fonte
0

crie uma matriz nsmutable e coloque todos os botões nessa matriz usint [array addObject: yourButton];

no método de pressionar o botão

-

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

Uma pequena variação na resposta dos Cocoanuts (que me ajudou a resolver isso) quando o botão estava no rodapé de uma tabela (o que impede você de encontrar a 'célula clicada':

-(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];
}
software evoluiu
fonte
0

Eu sempre uso tags.

Você precisa subclassificar UITableviewCelle manipular o botão pressionado a partir daí.

Chris
fonte
Eu não entendo bem como. A propriedade tag é configurada durante a criação da célula - essa célula é reutilizável para cada linha com o mesmo identificador. Essa tag é específica para o controle em uma célula reutilizável genérica. Como posso usar essa tag para diferenciar botões em células criadas de maneira genérica? Você poderia postar algum código?
rédea
0

É simples; crie uma célula personalizada e retire o botão

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

    cell.yourButton.tag = indexPath.Row;

- (void)buttonPressedAction:(id)sender

altere o ID no método acima para (UIButton *)

Você pode obter o valor em que botão está sendo pressionado executando sender.tag.

piyush Bageria
fonte
0

Subclasse o botão para armazenar o valor necessário, talvez crie um protocolo (ControlWithData ou algo assim). Defina o valor ao adicionar o botão à célula de exibição de tabela. No evento de retoque, verifique se o remetente obedece ao protocolo e extrai os dados. Normalmente, guardo uma referência ao objeto real que é renderizado na célula de exibição de tabela.

Jerome Chan Yeow Heong
fonte
0

ATUALIZAÇÃO SWIFT 2

Aqui está como descobrir qual botão foi tocado + enviar dados para outro ViewController a partir desse botão, indexPath.rowpois estou assumindo que esse é o ponto para a maioria!

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

    }

Para aqueles que estão usando uma classe ViewController e adicionaram um tableView, estou usando um ViewController em vez de um TableViewController, então adicionei manualmente o tableView para acessá-lo.

Aqui está o código para passar dados para outro VC ao tocar nesse botão e passar a célula 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
fonte
0

Observe aqui que estou usando uma célula personalizada. Este código está funcionando perfeitamente para mim.

 @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
fonte
0

A solução de Chris Schwerdt, mas depois em Swift, funcionou para mim:

@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
fonte
0

Este problema tem duas partes:

1) Obtendo o caminho do índice UITableViewCellque contém pressionadoUIButton

Existem algumas sugestões como:

  • Atualização UIButtoné tagno cellForRowAtIndexPath:método utilizando do caminho de índice rowde valor. Esta não é uma boa solução, pois requer atualização tagcontínua e não funciona com exibições de tabela com mais de uma seção.

  • Adicionando uma NSIndexPathpropriedade para celular personalizado e atualizá-lo em vez de UIButton's tagno cellForRowAtIndexPath:método. Isso resolve o problema de várias seções, mas ainda não é bom, pois exige atualização sempre.

  • Manter uma referência fraca ao pai UITableViewna célula personalizada enquanto a cria e usa o indexPathForCell:método para obter o caminho do índice. Parece um pouco melhor, não há necessidade de atualizar nada no cellForRowAtIndexPath:método, mas ainda requer a definição de uma referência fraca quando a célula personalizada é criada.

  • Usando a superViewpropriedade da célula para obter uma referência ao pai UITableView. Não é necessário adicionar propriedades à célula personalizada e não é necessário definir / atualizar nada na criação / posteriormente. Mas a célula superViewdepende dos detalhes de implementação do iOS. Portanto, não pode ser usado diretamente.

Mas isso pode ser alcançado usando um loop simples, pois temos certeza de que a célula em questão deve estar em um UITableView:

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

Portanto, essas sugestões podem ser combinadas em um método de célula personalizado simples e seguro para obter o caminho do índice:

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

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

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

A partir de agora, este método pode ser usado para detectar qual UIButtoné pressionado.

2) Informar outras partes sobre o evento de pressionar o botão

Depois de saber internamente qual UIButtoné pressionado em qual célula personalizada com o caminho exato do índice, essas informações precisam ser enviadas para outras partes (provavelmente o controlador de exibição que manipula o UITableView). Portanto, esse evento de clique no botão pode ser tratado em um nível de abstração e lógica semelhante ao didSelectRowAtIndexPath:método do delegado do UITableView.

Duas abordagens podem ser usadas para isso:

a) Delegação: a célula personalizada pode ter uma delegatepropriedade e definir um protocolo. Quando o botão é pressionado, ele apenas executa seus métodos de delegação em sua delegatepropriedade. Mas essa delegatepropriedade precisa ser configurada para cada célula customizada quando elas são criadas. Como alternativa, a célula personalizada também pode optar por executar seus métodos de delegação nas visualizações da tabela pai delegate.

b) Centro de Notificação: as células personalizadas podem definir um nome de notificação personalizado e postar essa notificação com o caminho do índice e as informações de exibição da tabela pai fornecidas no userInfoobjeto. Não é necessário definir nada para cada célula, basta adicionar um observador para a notificação da célula personalizada.

erkanyildiz
fonte
0

Eu uso uma solução que subclasse UIButtone pensei que deveria compartilhá-lo aqui, códigos em Swift:

class ButtonWithIndexPath : UIButton {
    var indexPath:IndexPath?
}

Lembre-se de atualizar seu indexPath em 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
}

Portanto, ao responder ao evento do botão, você pode usá-lo como

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