Usar una imagen personalizada para el accesorioView de UITableViewCell y hacer que responda a UITableViewDelegate

139

Estoy usando un UITableViewCell dibujado a medida, incluido el mismo para la celda accessoryView. Mi configuración para el accesorioView ocurre por algo como esto:

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

Además, cuando la celda se inicializa, initWithFrame:reuseIdentifier:me aseguré de establecer la siguiente propiedad:

self.userInteractionEnabled = YES;

Desafortunadamente en mi UITableViewDelegate, mi tableView:accessoryButtonTappedForRowWithIndexPath:método (intente repetir eso 10 veces) no se activa. El delegado definitivamente está conectado correctamente.

¿Qué puede faltar posiblemente?

Gracias a todos.


fuente

Respuestas:

228

Lamentablemente, ese método no se llama a menos que se toque el tipo de botón interno que se proporciona cuando usa uno de los tipos predefinidos. Para usar el suyo, tendrá que crear su accesorio como un botón u otra subclase de UIControl (recomendaría un botón usando -buttonWithType:UIButtonTypeCustomy configurando la imagen del botón, en lugar de usar un UIImageView).

Aquí hay algunas cosas que uso en Outpost, que personaliza lo suficiente de los widgets estándar (solo un poco, para que coincida con nuestro color verde azulado) que terminé haciendo mi propia subclase intermediaria UITableViewController para contener el código de utilidad para todas las demás vistas de tabla para usar (ahora subclase OPTableViewController).

En primer lugar, esta función devuelve un nuevo botón de divulgación de detalles utilizando nuestro gráfico personalizado:

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

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

    return ( button );
}

El botón llamará a esta rutina cuando termine, que luego alimenta la rutina estándar UITableViewDelegate para botones accesorios:

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

Esta función localiza la fila al obtener la ubicación en la vista de tabla de un toque del evento proporcionado por el botón y solicita a la vista de tabla la ruta de índice de la fila en ese punto.

Jim Dovey
fuente
Gracias Jim Es una pena que haya pasado más de 20 minutos preguntándome por qué no puedo hacerlo con una imagen personalizada. Acabo de ver cómo hacer esto en la aplicación de accesorios de muestra de Apple. Sin embargo, su respuesta está bien explicada y documentada, así que la marco y la mantengo. Gracias de nuevo. :-)
Jim, gran respuesta. Un problema potencial (al menos por mi parte): tuve que agregar la siguiente línea para obtener los toques para registrarme en el botón: button.userInteractionEnabled = YES;
Mike Laurence
11
Solo para los demás que miran esta respuesta, también puede poner una etiqueta en el botón que corresponde a la fila (si tiene varias secciones, deberá hacer algunos cálculos) y luego simplemente sacar la etiqueta del botón la función. Creo que podría ser un poco más rápido que calcular el toque.
RyanJM
3
esto requiere que codifiques el self.tableView. ¿Qué pasa si no sabe qué vista de tabla contiene la fila?
user102008
44
@RyanJM Solía ​​pensar que hacer un hitTest es excesivo y que las etiquetas serán suficientes. De hecho, he usado la idea de etiquetas en algunos de mis códigos. Pero hoy encontré un problema donde el usuario puede agregar nuevas filas. Esto mata al pirateo usando etiquetas. La solución sugerida por Jim Dovey (y como se ve en el código de muestra de Apple) es una solución genérica y funciona en todas las situaciones
srik
77

Este sitio web me pareció muy útil: vista de accesorios personalizados para su uitableview en iphone

En resumen, use esto en 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;

luego, 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
fuente
44
Diría +1 por esto, ya que es lo que Apple recomienda hacer en su código de muestra en sus documentos: developer.apple.com/library/ios/#samplecode/Accessory/Listings/…
cleverbit
Establecer el marco fue la pieza que faltaba para mí. También puede simplemente establecer Imagen (en lugar de fondo) siempre que no desee ningún texto.
Jeremy Hicks,
1
El enlace se ha roto en la respuesta de @ richarddas. Nuevo enlace: developer.apple.com/library/prerelease/ios/samplecode/Accessory/…
delavega66
7

Mi enfoque es crear una UITableViewCellsubclase y encapsular la lógica que llamará al UITableViewDelegatemétodo habitual dentro de ella.

// 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
fuente
Esta es la mejor respuesta. Pero button.superview, cell.superviewy [tableView.delegate tableView:...]no son lo suficientemente seguros.
Sr. Ming
3

Una extensión a la respuesta de Jim Dovey arriba:

Tenga cuidado cuando use un UISearchBarController con su UITableView. En ese caso, desea verificar self.searchDisplayController.activey usar en self.searchDisplayController.searchResultsTableViewlugar deself.tableView . De lo contrario, obtendrá resultados inesperados cuando searchDisplayController esté activo, especialmente cuando se desplazan los resultados de búsqueda.

Por ejemplo:

- (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
fuente
2
  1. Defina una macro para etiquetas de botones:

    #define AccessoryViewTagSinceValue 100000 // (AccessoryViewTagSinceValue * sections + rows) must be LE NSIntegerMax
  2. Crear botón y establecer el cell.accessoryView al crear una celda

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

    cell.accessoryView.tag = indexPath.section * AccessoryViewTagSinceValue + indexPath.row;
  4. Manejador de eventos para botones

    - (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 el método UITableViewDelegate

    - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
        // do sth.
    }
Señor ming
fuente
1
Nadie debe usarlo a tagmenos que sea absolutamente necesario, busque otra solución.
Lifely
2

Cuando se toca el botón, puede hacer que llame al siguiente método dentro de una subclase 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
fuente
1

Con el enfoque yanchenko tuve que agregar: [accBtn setFrame:CGRectMake(0, 0, 20, 20)];

Si está utilizando el archivo xib para personalizar su tableCell, entonces initWithStyle: reuseIdentifier: no se llamará.

En su lugar, anular:

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

[super awakeFromNib];

}
Toydor
fuente
1

Debe usar a UIControlpara obtener correctamente el envío de eventos (por ejemplo a UIButton) en lugar de simple UIView/UIImageView.

ikarius
fuente
1

Swift 5

Este enfoque utiliza el UIButton.tagpara almacenar el indexPath utilizando el desplazamiento de bits básico. El enfoque funcionará en sistemas de 32 y 64 bits siempre que no tenga más de 65535 secciones o filas.

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
fuente
0

A partir de iOS 3.2, puede evitar los botones que otros recomiendan y usar su UIImageView con un reconocedor de gestos táctiles. Asegúrese de habilitar la interacción del usuario, que está desactivada de manera predeterminada en UIImageViews.

aeu
fuente