Passer des paramètres à addTarget: action: forControlEvents

125

J'utilise addTarget: action: forControlEvents comme ceci:

[newsButton addTarget:self
action:@selector(switchToNewsDetails)
forControlEvents:UIControlEventTouchUpInside];

et je voudrais passer des paramètres à mon sélecteur "switchToNewsDetails". La seule chose que je réussis à faire est de passer l'expéditeur (id) en écrivant:

action:@selector(switchToNewsDetails:)

Mais j'essaye de passer des variables comme des valeurs entières. L'écrire de cette façon ne fonctionne pas:

int i = 0;
[newsButton addTarget:self
action:@selector(switchToNewsDetails:i)
forControlEvents:UIControlEventTouchUpInside];

L'écrire de cette façon ne fonctionne pas non plus:

int i = 0;
[newsButton addTarget:self
action:@selector(switchToNewsDetails:i:)
forControlEvents:UIControlEventTouchUpInside];

Toute aide serait appréciée :)

Pierre Espenan
la source
quelle est la signature de méthode pour switchToNewsDetails?
Aaron Saunders
- (void) switchToNewsDetails: (id) expéditeur; - (void) switchToNewsDetails: (int) i: (id) expéditeur;
Pierre Espenan
mais de quoi je dépend? est-ce spécifique pour chaque bouton? Voir ma réponse - La propriété de balise n'est-elle pas ce dont vous avez besoin?
Vladimir
@PierreEspenan Votre réponse actuelle acceptée ne permet que de passer des entiers via une balise, ce qui est très limitatif. Je vous exhorte à le changer en ma réponse qui permet le passage de TOUT objet.stackoverflow.com/a/40051706/2057171
Albert Renshaw

Réponses:

173
action:@selector(switchToNewsDetails:)

Vous ne passez pas de paramètres à la switchToNewsDetails:méthode ici. Vous venez de créer un sélecteur pour rendre le bouton capable de l'appeler lorsque certaines actions se produisent (retouchez dans votre cas). Les contrôles peuvent utiliser 3 types de sélecteurs pour répondre aux actions, tous ont une signification prédéfinie de leurs paramètres:

  1. sans paramètres

    action:@selector(switchToNewsDetails)
  2. avec 1 paramètre indiquant le contrôle qui envoie le message

    action:@selector(switchToNewsDetails:)
  3. Avec 2 paramètres indiquant le contrôle qui envoie le message et l'événement qui a déclenché le message:

    action:@selector(switchToNewsDetails:event:)

Ce que vous essayez de faire n'est pas clair, mais étant donné que vous souhaitez attribuer un index de détails spécifique à chaque bouton, vous pouvez effectuer les opérations suivantes:

  1. définir une propriété de balise pour chaque bouton égale à l'index requis
  2. dans la switchToNewsDetails:méthode, vous pouvez obtenir cet index et ouvrir les détails appropriés:

    - (void)switchToNewsDetails:(UIButton*)sender{
        [self openDetails:sender.tag];
        // Or place opening logic right here
    }
Vladimir
la source
4
et si nous essayons de passer autre chose que des entiers? et si j'ai besoin de passer un objet comme une chaîne?
user102008
@user quel est votre contexte? Il semble que vous deviez le passer séparément
Vladimir
Merci beaucoup. où avez-vous trouvé ces données? J'apprécie toujours la référence pour que je puisse peut-être la retrouver moi-même la prochaine fois.
bearMountain
1
@bearMountain, vous pouvez consulter ce lien: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/… par exemple
Vladimir
3
Vous n'avez pas besoin de le passer séparément. Si vous passez un NSObject, vous pouvez simplement le dire [button.layer setValue:yourObject forKey:@"anyKey"];, puis dans la méthode, vérifiez simplement (objectClass *)[button.layer valueForKey:@"anyKey"];C'est comme une version plus gratuite de.tag
Albert Renshaw
64

Pour transmettre des paramètres personnalisés avec le bouton cliquez, il vous suffit de sous- classer UIButton .

(ASR est activé, il n'y a donc pas de version dans le code.)

C'est myButton.h

#import <UIKit/UIKit.h>

@interface myButton : UIButton {
    id userData;
}

@property (nonatomic, readwrite, retain) id userData;

@end

C'est myButton.m

#import "myButton.h"
@implementation myButton
@synthesize userData;
@end

Usage:

myButton *bt = [myButton buttonWithType:UIButtonTypeCustom];
[bt setFrame:CGRectMake(0,0, 100, 100)];
[bt setExclusiveTouch:NO];
[bt setUserData:**(insert user data here)**];

[bt addTarget:self action:@selector(touchUpHandler:) forControlEvents:UIControlEventTouchUpInside];

[view addSubview:bt];

Fonction de réception:

- (void) touchUpHandler:(myButton *)sender {
    id userData = sender.userData;
}

Si vous avez besoin que je sois plus précis sur une partie du code ci-dessus, n'hésitez pas à le poser dans les commentaires.

Yuriy Polezhayev
la source
Je pense que c'est le meilleur moyen
Çağatay Gürtürk
Solution la plus élégante.
Victor C.
2
Grande réponse ... très personnalisable. Exactement ce que je cherchais. MOLODETS! :)
denikov
merci pour les commentaires :) Heureux, que ce soit utile pour quelqu'un :)
Yuriy Polezhayev
1
Solution de contournement simple et utile. Permettez-moi d'ajouter que cela est similaire à la tagpropriété dans les vues Android . Ils peuvent stocker n'importe quel objet. Et vous pouvez également stocker des objets par clé comme dans un dictionnaire / carte.
Ferran Maylinch
19

Target-Action permet trois formes différentes de sélecteur d'action:

- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event
Benoît
la source
16

Besoin de plus qu'un simple (int) via .tag? Utilisez KVC!

Vous pouvez transmettre toutes les données souhaitées via l'objet bouton lui-même (en accédant à CALayers keyValue dict).


Définissez votre objectif comme ceci (avec le ":")

[myButton addTarget:self action:@selector(buttonTap:) forControlEvents:UIControlEventTouchUpInside];

Ajoutez vos données au bouton lui-même (ainsi que celui .layerdu bouton) comme ceci:

NSString *dataIWantToPass = @"this is my data";//can be anything, doesn't have to be NSString
[myButton.layer setValue:dataIWantToPass forKey:@"anyKey"];//you can set as many of these as you'd like too!

Ensuite, lorsque vous appuyez sur le bouton, vous pouvez le vérifier comme ceci:

-(void)buttonTap:(UIButton*)sender{

    NSString *dataThatWasPassed = (NSString *)[sender.layer valueForKey:@"anyKey"];
    NSLog(@"My passed-thru data was: %@", dataThatWasPassed);

}
Albert Renshaw
la source
il n'y a pas de méthode dans UIButton.layer pour définir ou ajouter un objet. Où obtenez-vous cette solution?
mAc
1
Un UIButton est un type d'UIView, tous les UIViews ont la propriété CALayer .layer. CALayer contient des comportements NSDictionary. Notez que c'est Objective-C pas Swift, qui peut être le problème? Voici les documents d'Apple sur le codage des valeurs-clés CALayer: developer.apple.com/library/content/documentation/Cocoa/…
Albert Renshaw
1
Cela devrait être la réponse au dessus. De loin le moyen le plus simple de le faire !. Merci!
danielrosero
1
Cela devrait être une réponse acceptée.C'est une manière beaucoup plus simple et intelligente.
Emon
1
Très bonne réponse! J'aurais aimé le savoir il y a longtemps.
John
7

J'ai fait une solution basée en partie sur les informations ci-dessus. Je viens de définir le titlelabel.text sur la chaîne que je veux passer, et de définir le titlelabel.hidden = YES

Comme ça :

UIButton *imageclick = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
imageclick.frame = photoframe;
imageclick.titleLabel.text = [NSString stringWithFormat:@"%@.%@", ti.mediaImage, ti.mediaExtension];
imageclick.titleLabel.hidden = YES;

De cette façon, il n'y a pas besoin d'héritage ou de catégorie et il n'y a pas de fuite de mémoire

Abhinav Singh
la source
5

Je créais plusieurs boutons pour chaque numéro de téléphone dans un tableau de sorte que chaque bouton avait besoin d'un numéro de téléphone différent pour appeler. J'ai utilisé la fonction setTag car je créais plusieurs boutons dans une boucle for:

for (NSInteger i = 0; i < _phoneNumbers.count; i++) {

    UIButton *phoneButton = [[UIButton alloc] initWithFrame:someFrame];
    [phoneButton setTitle:_phoneNumbers[i] forState:UIControlStateNormal];

    [phoneButton setTag:i];

    [phoneButton addTarget:self
                    action:@selector(call:)
          forControlEvents:UIControlEventTouchUpInside];
}

Ensuite, dans mon appel: méthode, j'ai utilisé la même boucle for et une instruction if pour choisir le bon numéro de téléphone:

- (void)call:(UIButton *)sender
{
    for (NSInteger i = 0; i < _phoneNumbers.count; i++) {
        if (sender.tag == i) {
            NSString *callString = [NSString stringWithFormat:@"telprompt://%@", _phoneNumbers[i]];
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:callString]];
        }
    }
}
rjdunwoody91
la source
vous pouvez améliorer ce code:- (void)call:(UIButton *)sender { NSString *phoneNumber = _phoneNumbers[i]; NSString *callString = [NSString stringWithFormat:@"telprompt://%@", phoneNumber]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:callString]]; }
Ladessa
2

Comme il existe de nombreuses façons mentionnées ici pour la solution, sauf la fonction de catégorie.

Utilisez la fonction de catégorie pour étendre l'élément défini (intégré) dans votre élément personnalisable.

Par exemple (ex):

@interface UIButton (myData)

@property (strong, nonatomic) id btnData;

@end

dans votre vue Controller.m

 #import "UIButton+myAppLists.h"

UIButton *myButton = // btn intialisation....
 [myButton set btnData:@"my own Data"];
[myButton addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];

Gestionnaire d'événements:

-(void)buttonClicked : (UIButton*)sender{
    NSLog(@"my Data %@", sender. btnData);
}
Kumar KL
la source
Vous ne pouvez pas vraiment ajouter de propriétés dans les catégories.
HotFudge dimanche
2

Vous pouvez remplacer l'action-cible par une fermeture (bloc en Objective-C) en ajoutant un wrapper de fermeture d'assistance (ClosureSleeve) et en l'ajoutant en tant qu'objet associé au contrôle afin qu'il soit conservé. De cette façon, vous pouvez passer tous les paramètres.

Swift 3

class ClosureSleeve {
    let closure: () -> ()

    init(attachTo: AnyObject, closure: @escaping () -> ()) {
        self.closure = closure
        objc_setAssociatedObject(attachTo, "[\(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN)
    }

    @objc func invoke() {
        closure()
    }
}

extension UIControl {
    func addAction(for controlEvents: UIControlEvents, action: @escaping () -> ()) {
        let sleeve = ClosureSleeve(attachTo: self, closure: action)
        addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
    }
}

Usage:

button.addAction(for: .touchUpInside) {
    self.switchToNewsDetails(parameter: i)
}
Marián Černý
la source
Ci-dessus la méthode ( self.switchToNewsDetails(parameter: i)) tirant 2 fois, pourquoi m'aider
Nagendar
Le code fonctionne pour moi. Testé dans un nouveau projet d'application à vue unique pastebin.com/tPaqetMb . Le problème est donc probablement dans votre code, comme appeler button.addAction()deux fois. Ajoutez un point d'arrêt à button.addActionet également sur la première ligne à l'intérieur de la fermeture et essayez de le déboguer vous-même.
Marián Černý
2

Il existe un autre moyen, dans lequel vous pouvez obtenir indexPath de la cellule où votre bouton a été enfoncé:

en utilisant un sélecteur d'action habituel comme:

 UIButton *btn = ....;
    [btn addTarget:self action:@selector(yourFunction:) forControlEvents:UIControlEventTouchUpInside];

puis dans votre fonction:

   - (void) yourFunction:(id)sender {

    UIButton *button = sender;
    CGPoint center = button.center;
    CGPoint rootViewPoint = [button.superview convertPoint:center toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rootViewPoint];
    //the rest of your code goes here
    ..
}

puisque vous obtenez un indexPath, il est devenu beaucoup plus simple.

Anton Lisitsyn
la source
1

Voir mon commentaire ci-dessus, et je pense que vous devez utiliser NSInvocation lorsqu'il y a plus d'un paramètre

plus d'informations sur NSInvocation ici

http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

Aaron Saunders
la source
En fait, je ne veux pas passer plusieurs paramètres, je veux juste un entier.
Pierre Espenan
en fait il y a performSelector: withObject: withObject: méthode qui permet d'appeler des sélecteurs avec 2 paramètres. Mais pour plus, vous avez besoin de NSInvocation
Vladimir
1

Cela a résolu mon problème mais il s'est écrasé à moins que je ne change

action:@selector(switchToNewsDetails:event:)                 

à

action:@selector(switchToNewsDetails: forEvent:)              
iphaaw
la source
0

J'ai sous-classé UIButton dans CustomButton et j'ajoute une propriété où je stocke mes données. J'appelle donc method: (CustomButton *) sender et dans la méthode je ne lis que mes données sender.myproperty.

Exemple CustomButton:

@interface CustomButton : UIButton
@property(nonatomic, retain) NSString *textShare;
@end

Action de la méthode:

+ (void) share: (CustomButton*) sender
{
    NSString *text = sender.textShare;
    //your work…
}

Attribuer une action

    CustomButton *btn = [[CustomButton alloc] initWithFrame: CGRectMake(margin, margin, 60, 60)];
    // other setup…

    btnWa.textShare = @"my text";
    [btn addTarget: self action: @selector(shareWhatsapp:)  forControlEvents: UIControlEventTouchUpInside];
iNiko84
la source
0

Si vous souhaitez simplement modifier le texte du leftBarButtonItem affiché par le contrôleur de navigation avec la nouvelle vue, vous pouvez modifier le titre de la vue actuelle juste avant d'appeler pushViewController au texte souhaité et le restaurer dans le rappel viewHasDisappered pour les futures expositions de la vue actuelle.

Cette approche conserve la fonctionnalité (popViewController) et l'apparence de la flèche affichée intacts.

Cela fonctionne pour nous au moins avec iOS 12, construit avec Xcode 10.1 ...

Bubu
la source
Je ne pense pas que cette réponse soit liée à la question.
Pierre Espenan