Pasando parámetros a addTarget: action: forControlEvents

125

Estoy usando addTarget: action: forControlEvents como este:

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

y me gustaría pasar parámetros a mi selector "switchToNewsDetails". Lo único que logro hacer es pasar al remitente (id) escribiendo:

action:@selector(switchToNewsDetails:)

Pero estoy tratando de pasar variables como valores enteros. Escribirlo de esta manera no funciona:

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

Escribirlo de esta manera tampoco funciona:

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

Cualquier ayuda sería apreciada :)

Pierre Espenan
fuente
¿Cuál es la firma del método para switchToNewsDetails?
Aaron Saunders
- (nulo) switchToNewsDetails: (id) remitente; - (nulo) switchToNewsDetails: (int) i: (id) remitente;
Pierre Espenan
pero de que depende? ¿Es específico para cada botón? Vea mi respuesta: ¿no es la propiedad de etiqueta lo que necesita?
Vladimir
@PierreEspenan Su respuesta actual aceptada solo permite pasar enteros a través de etiquetas, lo cual es muy limitante. Le insto a que lo cambie a mi respuesta que permite el paso de CUALQUIER objeto. stackoverflow.com/a/40051706/2057171
Albert Renshaw el

Respuestas:

173
action:@selector(switchToNewsDetails:)

No pasa parámetros al switchToNewsDetails:método aquí. Simplemente cree un selector para que el botón pueda llamarlo cuando ocurra cierta acción (retoque en su caso). Los controles pueden usar 3 tipos de selectores para responder a las acciones, todos ellos tienen un significado predefinido de sus parámetros:

  1. sin parámetros

    action:@selector(switchToNewsDetails)
  2. con 1 parámetro que indica el control que envía el mensaje

    action:@selector(switchToNewsDetails:)
  3. Con 2 parámetros que indican el control que envía el mensaje y el evento que activó el mensaje:

    action:@selector(switchToNewsDetails:event:)

No está claro qué es exactamente lo que intenta hacer, pero teniendo en cuenta que desea asignar un índice de detalles específico a cada botón, puede hacer lo siguiente:

  1. establecer una propiedad de etiqueta para cada botón igual al índice requerido
  2. En el switchToNewsDetails:método puede obtener ese índice y abrir detalles apropiados:

    - (void)switchToNewsDetails:(UIButton*)sender{
        [self openDetails:sender.tag];
        // Or place opening logic right here
    }
Vladimir
fuente
44
¿Qué pasa si estamos tratando de pasar algo que no sean enteros? ¿Qué pasa si necesito pasar un objeto como una cadena?
user102008
@user ¿cuál es tu contexto? Parece que tendrá que pasarlo por separado
Vladimir
muchas gracias. ¿Dónde encontraste esta información? Siempre aprecio la referencia para que tal vez pueda encontrarla la próxima vez.
bearMountain
1
@bearMountain puedes consultar este enlace: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/… por ejemplo
Vladimir
3
No necesita pasarlo por separado. Si está pasando cualquier NSObject, simplemente puede decirlo [button.layer setValue:yourObject forKey:@"anyKey"];y luego, en el método, simplemente marque (objectClass *)[button.layer valueForKey:@"anyKey"];Es como una versión más gratuita de.tag
Albert Renshaw el
64

Para pasar parámetros personalizados junto con el botón, solo necesita SUBCLASS UIButton .

(ASR está activado, por lo que no hay versiones en el código).

Este es myButton.h

#import <UIKit/UIKit.h>

@interface myButton : UIButton {
    id userData;
}

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

@end

Este es myButton.m

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

Uso:

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

Función de recepción:

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

Si necesita que sea más específico en cualquier parte del código anterior, no dude en preguntarlo en los comentarios.

Yuriy Polezhayev
fuente
Creo que esta es la mejor manera
Çağatay Gürtürk
La solución más elegante.
Victor C.
2
Gran respuesta ... muy personalizable. Exactamente lo que estaba buscando. MOLODETS! :)
denikov
gracias por los comentarios :) Me alegro de que sea útil para alguien :)
Yuriy Polezhayev
1
Solución simple y útil. Solo permítanme agregar que esto es similar a la tagpropiedad en las vistas de Android . Pueden almacenar cualquier objeto. Y también puede almacenar objetos por clave como en un Diccionario / Mapa.
Ferran Maylinch
19

Target-Action permite tres formas diferentes de selector de acciones:

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

¿Necesita más que solo un (int) a través de .tag? Use KVC!

Puede pasar cualquier dato que desee a través del objeto de botón en sí (accediendo a CALayers keyValue dict).


Establezca su objetivo de esta manera (con el ":")

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

Agregue su (s) información (es) al botón en sí (así como .layeral botón que está) así:

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!

Luego, cuando se toca el botón, puede verificarlo así:

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

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

}
Albert Renshaw
fuente
no hay ningún método en UIButton.layer para establecer o agregar un objeto. ¿De dónde sacas esa solución?
mAc
1
Un UIButton es un tipo de UIView, todas las UIViews tienen la propiedad CALayer .layer. CALayer contiene comportamientos NSDictionary. Tenga en cuenta que esto es Objective-C no Swift, ¿ese puede ser el problema? Aquí están los documentos de Apple sobre la codificación de valores clave de CALayer: developer.apple.com/library/content/documentation/Cocoa/…
Albert Renshaw
1
Esta debería ser la respuesta en la parte superior. ¡Con mucho, la forma más fácil de hacerlo! ¡Gracias!
danielrosero
1
Se debe aceptar la respuesta. Es una forma mucho más fácil e inteligente.
Emon
1
¡Gran respuesta! Ojalá hubiera sabido esto hace mucho tiempo.
John
7

Hice una solución basada en parte en la información anterior. Simplemente configuré titlelabel.text en la cadena que quiero pasar y configuré titlelabel.hidden = YES

Me gusta esto :

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

De esta manera, no hay necesidad de una herencia o categoría y no hay pérdida de memoria

Abhinav Singh
fuente
5

Estaba creando varios botones para cada número de teléfono en una matriz, por lo que cada botón necesitaba un número de teléfono diferente para llamar. Usé la función setTag mientras creaba varios botones dentro de un bucle 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];
}

Luego, en mi llamada: método, utilicé el mismo for loop y una instrucción if para elegir el número de teléfono correcto:

- (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
fuente
puedes mejorar este código:- (void)call:(UIButton *)sender { NSString *phoneNumber = _phoneNumbers[i]; NSString *callString = [NSString stringWithFormat:@"telprompt://%@", phoneNumber]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:callString]]; }
Ladessa
2

Como hay muchas formas mencionadas aquí para la solución, excepto la función de categoría.

Use la función de categoría para extender el elemento definido (incorporado) a su elemento personalizable.

Por ejemplo (ex):

@interface UIButton (myData)

@property (strong, nonatomic) id btnData;

@end

en su vista Controlador.m

 #import "UIButton+myAppLists.h"

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

Controlador de eventos:

-(void)buttonClicked : (UIButton*)sender{
    NSLog(@"my Data %@", sender. btnData);
}
Kumar KL
fuente
Realmente no se pueden agregar propiedades en categorías.
HotFudgeSunday
2

Puede reemplazar la acción del objetivo con un cierre (bloque en Objective-C) agregando un envoltorio de cierre auxiliar (ClosureSleeve) y agregándolo como un objeto asociado al control para que se retenga. De esa manera puede pasar cualquier parámetro.

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

Uso:

button.addAction(for: .touchUpInside) {
    self.switchToNewsDetails(parameter: i)
}
Marián Černý
fuente
Método anterior ( self.switchToNewsDetails(parameter: i)) disparando 2 veces, ¿por qué ayudarme?
Nagendar
El código me funciona. Probado en un nuevo proyecto de aplicación de vista única pastebin.com/tPaqetMb . Entonces, el problema probablemente esté en su código, como llamar button.addAction()dos veces. Agregue un punto de interrupción button.addActiony también en la primera línea dentro del cierre e intente depurarlo usted mismo.
Marián Černý
2

Hay otra forma, en la que puede obtener indexPath de la celda donde se presionó el botón:

usando el selector de acción habitual como:

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

y luego en su función:

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

desde que obtienes indexPath se vuelve mucho más simple.

Anton Lisitsyn
fuente
1

Vea mi comentario anterior, y creo que debe usar NSInvocation cuando hay más de un parámetro

Más información sobre NSInvocation aquí

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

Aaron Saunders
fuente
En realidad, no quiero pasar varios parámetros, solo quiero un número entero.
Pierre Espenan
en realidad hay performSelector: withObject: withObject: método que permite llamar a los selectores con 2 parámetros. Pero para más necesitas NSInvocation
Vladimir
1

Esto solucionó mi problema pero se bloqueó a menos que cambiara

action:@selector(switchToNewsDetails:event:)                 

a

action:@selector(switchToNewsDetails: forEvent:)              
iphaaw
fuente
0

Subclasifiqué UIButton en CustomButton y agregué una propiedad donde almaceno mis datos. Entonces llamo método: (CustomButton *) remitente y en el método solo leo mi data sender.myproperty.

Ejemplo de CustomButton:

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

Método de acción:

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

Asignar acción

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

Si solo desea cambiar el texto para el leftBarButtonItem que muestra el controlador de navegación junto con la nueva vista, puede cambiar el título de la vista actual justo antes de llamar a pushViewController al texto deseado y restaurarlo en la devolución de llamada viewHasDisappered para futuras presentaciones de La vista actual.

Este enfoque mantiene la funcionalidad (popViewController) y la apariencia de la flecha que se muestra intacta.

Al menos funciona para nosotros con iOS 12, construido con Xcode 10.1 ...

Bubu
fuente
No creo que esta respuesta esté relacionada con la pregunta.
Pierre Espenan