Usando uma imagem personalizada para o acessórioView de um UITableViewCell e respondendo a UITableViewDelegate

139

Estou usando um UITableViewCell de desenho personalizado, incluindo o mesmo para a célula accessoryView. Minha configuração para o acessórioView acontece da seguinte maneira:

UIImage *accessoryImage = [UIImage imageNamed:@"accessoryDisclosure.png"];
UIImageView *accImageView = [[UIImageView alloc] initWithImage:accessoryImage];
accImageView.userInteractionEnabled = YES;
[accImageView setFrame:CGRectMake(0, 0, 28.0, 28.0)];
self.accessoryView = accImageView;
[accImageView release];

Além disso, quando a célula é inicializada, o uso de initWithFrame:reuseIdentifier:I garantiu a configuração da seguinte propriedade:

self.userInteractionEnabled = YES;

Infelizmente no meu UITableViewDelegate, meu tableView:accessoryButtonTappedForRowWithIndexPath:método (tente repetir 10 vezes) não está sendo acionado. O delegado está definitivamente conectado corretamente.

O que pode estar faltando?

Obrigado a todos.


fonte

Respostas:

228

Infelizmente, esse método não é chamado, a menos que o tipo de botão interno fornecido quando você usa um dos tipos predefinidos seja tocado. Para usar o seu, você precisará criar seu acessório como um botão ou outra subclasse UIControl (eu recomendo um botão usando -buttonWithType:UIButtonTypeCustome definindo a imagem do botão, em vez de usar um UIImageView).

Aqui estão algumas coisas que eu uso no Outpost, que personaliza o suficiente dos widgets padrão (apenas um pouco, para combinar com a cor verde-azulado) que acabei fazendo minha própria subclasse intermediária UITableViewController para manter o código de utilidade para todas as outras visualizações de tabela a usar (agora elas subclasses OPTableViewController).

Primeiramente, essa função retorna um novo botão de divulgação de detalhes usando nosso gráfico personalizado:

- (UIButton *) makeDetailDisclosureButton
{
    UIButton * button = [UIButton outpostDetailDisclosureButton];

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

    return ( button );
}

O botão chamará essa rotina quando terminar, que alimentará a rotina UITableViewDelegate padrão para os botões acessórios:

- (void) accessoryButtonTapped: (UIControl *) button withEvent: (UIEvent *) event
{
    NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint: [[[event touchesForView: button] anyObject] locationInView: self.tableView]];
    if ( indexPath == nil )
        return;

    [self.tableView.delegate tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}

Essa função localiza a linha obtendo o local na visualização da tabela de um toque do evento fornecido pelo botão e solicitando à visualização da tabela o caminho do índice da linha nesse ponto.

Jim Dovey
fonte
Obrigado Jim. É uma pena que passei mais de 20 minutos me perguntando por que não posso fazer isso com um imageView personalizado. Acabei de ver como fazer isso no aplicativo acessório de exemplo da Apple. Sua resposta está bem explicada e documentada, por isso estou marcando e mantendo-a por perto. Obrigado novamente. :-)
Jim, ótima resposta. Um problema em potencial (pelo menos do meu lado) - tive que adicionar a seguinte linha para obter os retoques registrados no botão: button.userInteractionEnabled = YES;
357 Mike Laurence
11
Apenas para outras pessoas que olham para esta resposta, você também pode colocar uma tag no botão que corresponde à linha (se você tiver várias seções, precisará fazer algumas contas) e depois puxar a tag para fora do botão a função. Eu acho que pode ser um pouco mais rápido do que calcular o toque.
RyanJM
3
isso requer que você codifique o arquivo self.tableView. e se você não souber qual tableview contém a linha?
user102008
4
@RyanJM Eu costumava pensar que fazer um hitTest é um exagero e as tags serão suficientes. Na verdade, usei a idéia de tags em alguns dos meus códigos. Mas hoje encontrei um problema em que o usuário pode adicionar novas linhas. Isso mata o hack usando tags. A solução sugerida por Jim Dovey (e como visto no código de exemplo da Apple) é uma solução genérica e obras em todas as situações
srik
77

Achei este site muito útil: visualização de acessórios personalizados para a sua visualização uitabl no iphone

Em resumo, use isso em cellForRowAtIndexPath::

UIImage *image = (checked) ? [UIImage imageNamed:@"checked.png"] : [UIImage imageNamed:@"unchecked.png"];

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
button.frame = frame;
[button setBackgroundImage:image forState:UIControlStateNormal];

[button addTarget:self action:@selector(checkButtonTapped:event:)  forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;

em seguida, implemente este método:

- (void)checkButtonTapped:(id)sender event:(id)event
{
    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    CGPoint currentTouchPosition = [touch locationInView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];

    if (indexPath != nil)
    {
        [self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
    }
}
Jon
fonte
4
Eu diria +1 para isso, pois é o que a Apple recomenda fazer no código de exemplo em seus documentos: developer.apple.com/library/ios/#samplecode/Accessory/Listings/…
cleverbit
Definir a moldura foi a peça que faltava para mim. Você também pode definir ImageImage (em vez de segundo plano), desde que também não queira nenhum texto.
Jeremy Hicks
1
O link foi quebrado na resposta de @ richarddas. Novo link: developer.apple.com/library/prerelease/ios/samplecode/Accessory/…
delavega66
7

Minha abordagem é criar uma UITableViewCellsubclasse e encapsular a lógica que chamará o UITableViewDelegatemétodo usual dentro dela.

// CustomTableViewCell.h
@interface CustomTableViewCell : UITableViewCell

- (id)initForIdentifier:(NSString *)reuseIdentifier;

@end

// CustomTableViewCell.m
@implementation CustomTableViewCell

- (id)initForIdentifier:(NSString *)reuseIdentifier;
{
    // the subclass specifies style itself
    self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier];
    if (self) {
        // get the button elsewhere
        UIButton *accBtn = [ViewFactory createTableViewCellDisclosureButton];
        [accBtn addTarget: self
                   action: @selector(accessoryButtonTapped:withEvent:)
         forControlEvents: UIControlEventTouchUpInside];
        self.accessoryView = accBtn;
    }
    return self;
}

#pragma mark - private

- (void)accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event
{
    UITableViewCell *cell = (UITableViewCell*)button.superview;
    UITableView *tableView = (UITableView*)cell.superview;
    NSIndexPath *indexPath = [tableView indexPathForCell:cell];
    [tableView.delegate tableView:tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
}

@end
yanchenko
fonte
Esta é a melhor resposta. Mas button.superview, cell.superviewe [tableView.delegate tableView:...]não são seguros o suficiente.
Mr. Ming
3

Uma extensão da resposta de Jim Dovey acima:

Tenha cuidado ao usar um UISearchBarController com seu UITableView. Nesse caso, você deseja verificar self.searchDisplayController.activee usar em self.searchDisplayController.searchResultsTableViewvez de self.tableView. Caso contrário, você obterá resultados inesperados quando o searchDisplayController estiver ativo, especialmente quando os resultados da pesquisa forem rolados.

Por exemplo:

- (void) accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event
{
    UITableView* tableView = self.tableView;
    if(self.searchDisplayController.active)
        tableView = self.searchDisplayController.searchResultsTableView;

    NSIndexPath * indexPath = [tableView indexPathForRowAtPoint:[[[event touchesForView:button] anyObject] locationInView:tableView]];
    if(indexPath)
       [tableView.delegate tableView:tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
}
Zaggo
fonte
2
  1. Defina uma macro para tags de botões:

    #define AccessoryViewTagSinceValue 100000 // (AccessoryViewTagSinceValue * sections + rows) must be LE NSIntegerMax
  2. Botão Criar e defina o cell.accessoryView ao criar uma célula

    UIButton *accessoryButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
    accessoryButton.frame = CGRectMake(0, 0, 30, 30);
    [accessoryButton addTarget:self action:@selector(accessoryButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
    cell.accessoryView = accessoryButton;
  3. Defina cell.accessoryView.tag por indexPath no método UITableViewDataSource -tableView: cellForRowAtIndexPath:

    cell.accessoryView.tag = indexPath.section * AccessoryViewTagSinceValue + indexPath.row;
  4. Manipulador de eventos para botões

    - (void) accessoryButtonTapped:(UIButton *)button {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:button.tag % AccessoryViewTagSinceValue
                                                    inSection:button.tag / AccessoryViewTagSinceValue];
    
        [self.tableView.delegate tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
    }
  5. Implemente o método UITableViewDelegate

    - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
        // do sth.
    }
Mr. Ming
fonte
1
Ninguém deve usar, a tagmenos que seja absolutamente necessário, procure outra solução.
Lifely
2

Quando o botão é tocado, você pode chamar o método a seguir dentro de uma subclasse UITableViewCell

 -(void)buttonTapped{
     // perform an UI updates for cell

     // grab the table view and notify it using the delegate
     UITableView *tableView = (UITableView *)self.superview;
     [tableView.delegate tableView:tableView accessoryButtonTappedForRowWithIndexPath:[tableView indexPathForCell:self]];

 }
Eric Welander
fonte
1

Com a abordagem yanchenko, eu tive que adicionar: [accBtn setFrame:CGRectMake(0, 0, 20, 20)];

Se você estiver usando o arquivo xib para personalizar seu tableCell, o initWithStyle: reuseIdentifier: não será chamado.

Em vez disso, substitua:

-(void)awakeFromNib
{
//Put your code here 

[super awakeFromNib];

}
Toydor
fonte
1

Você deve usar a UIControlpara obter corretamente o envio de eventos (por exemplo, a UIButton) em vez de simples UIView/UIImageView.

ikarius
fonte
1

Swift 5

Essa abordagem usa o UIButton.tagpara armazenar o indexPath usando deslocamento de bits básico. A abordagem funcionará em sistemas de 32 e 64 bits, desde que você não tenha mais de 65535 seções ou linhas.

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

    let cell = tableView.dequeueReusableCell(withIdentifier: "cellId")
    let accessoryButton = UIButton(type: .custom)
    accessoryButton.setImage(UIImage(named: "imageName"), for: .normal)
    accessoryButton.sizeToFit()
    accessoryButton.addTarget(self, action: #selector(handleAccessoryButton(sender:)), for: .touchUpInside)

    let tag = (indexPath.section << 16) | indexPath.row
    accessoryButton.tag = tag
    cell?.accessoryView = accessoryButton

}

@objc func handleAccessoryButton(sender: UIButton) {
    let section = sender.tag >> 16
    let row = sender.tag & 0xFFFF
    // Do Stuff
}
Brody Robertson
fonte
0

No iOS 3.2, você pode evitar os botões recomendados pelos outros aqui e, em vez disso, usar o UIImageView com um reconhecedor de gestos. Certifique-se de ativar a interação do usuário, que está desativada por padrão no UIImageViews.

aeu
fonte