Utiliser une image personnalisée pour un accessoireView d'un UITableViewCell et le faire répondre à UITableViewDelegate


139

J'utilise un UITableViewCell dessiné personnalisé, y compris le même pour la cellule accessoryView. Ma configuration pour l'accessoireView se produit par le biais de quelque chose comme ceci:

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

Aussi lorsque la cellule est initialisée, en utilisant initWithFrame:reuseIdentifier: je me suis assuré de définir la propriété suivante:

self.userInteractionEnabled = YES;

Malheureusement, dans mon UITableViewDelegate, ma tableView:accessoryButtonTappedForRowWithIndexPath:méthode (essayez de répéter cela 10 fois) ne se déclenche pas. Le délégué est définitivement câblé correctement.

Qu'est-ce qui peut éventuellement manquer?

Merci a tous.

Réponses:


228

Malheureusement, cette méthode n'est appelée que si le type de bouton interne fourni lorsque vous utilisez l'un des types prédéfinis est tapé. Pour utiliser le vôtre, vous devrez créer votre accessoire en tant que bouton ou autre sous-classe UIControl (je recommanderais un bouton utilisant -buttonWithType:UIButtonTypeCustomet définissant l'image du bouton, plutôt que d'utiliser une UIImageView).

Voici quelques éléments que j'utilise dans Outpost, qui personnalise suffisamment de widgets standard (juste légèrement, pour correspondre à notre coloration sarcelle) que j'ai fini par faire ma propre sous-classe intermédiaire UITableViewController pour contenir le code utilitaire pour toutes les autres vues de table à utiliser (ils sous-classe maintenant OPTableViewController).

Premièrement, cette fonction renvoie un nouveau bouton de divulgation des détails en utilisant notre graphique personnalisé:

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

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

    return ( button );
}

Le bouton appellera cette routine une fois terminé, qui alimentera ensuite la routine UITableViewDelegate standard pour les boutons accessoires:

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

Cette fonction localise la ligne en obtenant l'emplacement dans la vue de table d'une touche à partir de l'événement fourni par le bouton et en demandant à la vue de table le chemin d'index de la ligne à ce point.


Merci Jim. C'est dommage que j'ai passé plus de 20 minutes à me demander pourquoi je ne peux pas le faire avec une imageView personnalisée. Je viens de voir comment faire cela sur l'exemple d'application Accessory d'Apple. Votre réponse est bien expliquée et documentée, donc je la note et la garde. Merci encore. :-)

Jim, excellente réponse. Un problème potentiel (au moins de mon côté) - J'ai dû ajouter la ligne suivante pour obtenir les touches pour m'inscrire sur le bouton: button.userInteractionEnabled = YES;
Mike Laurence

11
Pour les autres qui regardent cette réponse, vous pouvez également simplement mettre une balise sur le bouton qui correspond à la ligne (si vous avez plusieurs sections, vous devrez faire des calculs), puis retirez simplement la balise du bouton dans la fonction. Je pense que cela pourrait être un peu plus rapide que de calculer le toucher.
RyanJM

3
cela vous oblige à coder en dur le fichier self.tableView. que faire si vous ne savez pas quelle vue de table contient la ligne?
user102008

4
@RyanJM J'avais l'habitude de penser que faire un hitTest est exagéré et que les balises suffiront. J'ai en fait utilisé l'idée de balises dans certains de mes codes. Mais aujourd'hui, j'ai rencontré un problème où l'utilisateur peut ajouter de nouvelles lignes. Cela tue le piratage en utilisant des balises. La solution suggérée par Jim Dovey (et comme on le voit dans l'exemple de code d'Apple) est une solution générique et fonctionne dans toutes les situations
srik

77

J'ai trouvé ce site Web très utile: vue des accessoires personnalisés pour votre uitableview sur iPhone

En bref, utilisez ceci dans 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;

puis, implémentez cette méthode:

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

4
Je dirais +1 pour cela, car c'est ce qu'Apple recommande de faire dans leur exemple de code dans leur documentation: developer.apple.com/library/ios/#samplecode/Accessory/Listings/…
cleverbit

Le réglage du cadre était la pièce manquante pour moi. Vous pouvez également simplement setImage (au lieu de background) tant que vous ne voulez pas de texte.
Jeremy Hicks

1
Le lien s'est rompu dans la réponse de @ richarddas. Nouveau lien: developer.apple.com/library/prerelease/ios/samplecode/Accessory/…
delavega66

7

Mon approche consiste à créer une UITableViewCellsous-classe et à encapsuler la logique qui appellera la UITableViewDelegateméthode habituelle en son sein.

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

C'est la meilleure réponse. Mais button.superview, cell.superviewet [tableView.delegate tableView:...]ne sont pas assez sûrs.
M. Ming

3

Une extension de la réponse de Jim Dovey ci-dessus:

Soyez prudent lorsque vous utilisez un UISearchBarController avec votre UITableView. Dans ce cas, vous souhaitez rechercher self.searchDisplayController.activeet utiliser à la self.searchDisplayController.searchResultsTableViewplace de self.tableView. Sinon, vous obtiendrez des résultats inattendus lorsque searchDisplayController est actif, en particulier lorsque les résultats de la recherche défilent.

Par exemple:

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

2
  1. Définissez une macro pour les balises des boutons:

    #define AccessoryViewTagSinceValue 100000 // (AccessoryViewTagSinceValue * sections + rows) must be LE NSIntegerMax
  2. Créer un bouton et définir le cell.accessoryView lors de la création d'une cellule

    UIButton *accessoryButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
    accessoryButton.frame = CGRectMake(0, 0, 30, 30);
    [accessoryButton addTarget:self action:@selector(accessoryButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
    cell.accessoryView = accessoryButton;
  3. Définissez cell.accessoryView.tag par indexPath dans la méthode UITableViewDataSource -tableView: cellForRowAtIndexPath:

    cell.accessoryView.tag = indexPath.section * AccessoryViewTagSinceValue + indexPath.row;
  4. Gestionnaire d'événements pour les boutons

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

    - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
        // do sth.
    }

1
Personne ne devrait utiliser tagsauf si c'est absolument nécessaire, recherchez une autre solution.
Lifely

2

Lorsque vous appuyez sur le bouton, vous pouvez lui faire appeler la méthode suivante dans une sous-classe 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]];

 }

1

Avec l'approche yanchenko, j'ai dû ajouter: [accBtn setFrame:CGRectMake(0, 0, 20, 20)];

Si vous utilisez un fichier xib pour personnaliser votre tableCell, initWithStyle: reuseIdentifier: ne sera pas appelé.

Remplacez plutôt:

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

[super awakeFromNib];

}

1

Vous devez utiliser a UIControlpour obtenir correctement la répartition des événements (par exemple a UIButton) au lieu de simple UIView/UIImageView.


1

Swift 5

Cette approche utilise UIButton.tagpour stocker l'indexPath à l'aide du décalage de bits de base. L'approche fonctionnera sur les systèmes 32 et 64 bits tant que vous n'avez pas plus de 65535 sections ou lignes.

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
}

0

À partir d'iOS 3.2, vous pouvez éviter les boutons que d'autres recommandent ici et utiliser à la place votre UIImageView avec un outil de reconnaissance de gestes. Assurez-vous d'activer l'interaction de l'utilisateur, qui est désactivée par défaut dans UIImageViews.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.