Passando parâmetros para addTarget: action: forControlEvents

125

Estou usando addTarget: action: forControlEvents como este:

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

e gostaria de passar parâmetros para o meu seletor "switchToNewsDetails". A única coisa que consigo fazer é passar o remetente (id) escrevendo:

action:@selector(switchToNewsDetails:)

Mas estou tentando passar variáveis ​​como valores inteiros. Escrever dessa maneira não funciona:

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

Escrever dessa maneira também não funciona:

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

Qualquer ajuda seria apreciada :)

Pierre Espenan
fonte
qual é a assinatura do método para switchToNewsDetails?
Aaron Saunders
- (void) switchToNewsDetails: (id) remetente; - (void) switchToNewsDetails: (int) i: (id) remetente;
Pierre Espenan
mas do que eu depende? é específico para cada botão? Veja minha resposta - a propriedade da tag não é o que você precisa?
Vladimir
@PierreEspenan Sua resposta atual aceita apenas permite a passagem de números inteiros via tag, o que é muito limitante. Peço que você mude para a minha resposta, que permite a passagem de QUALQUER objeto. stackoverflow.com/a/40051706/2057171
Albert Renshaw 14/16

Respostas:

173
action:@selector(switchToNewsDetails:)

Você não passa parâmetros para o switchToNewsDetails:método aqui. Você acabou de criar um seletor para tornar o botão capaz de chamá-lo quando determinada ação ocorrer (retocar no seu caso). Os controles podem usar 3 tipos de seletores para responder às ações, todos com significado predefinido de seus parâmetros:

  1. sem parâmetros

    action:@selector(switchToNewsDetails)
  2. com 1 parâmetro indicando o controle que envia a mensagem

    action:@selector(switchToNewsDetails:)
  3. Com 2 parâmetros indicando o controle que envia a mensagem e o evento que acionou a mensagem:

    action:@selector(switchToNewsDetails:event:)

Não está claro o que exatamente você tenta fazer, mas, considerando que deseja atribuir um índice de detalhes específico a cada botão, você pode fazer o seguinte:

  1. defina uma propriedade de tag para cada botão igual ao índice necessário
  2. No switchToNewsDetails:método, você pode obter esse índice e abrir detalhes apropriados:

    - (void)switchToNewsDetails:(UIButton*)sender{
        [self openDetails:sender.tag];
        // Or place opening logic right here
    }
Vladimir
fonte
4
e se estamos tentando passar algo diferente de números inteiros? e se eu precisar passar um objeto como uma string?
user102008
@user Qual é o seu contexto? Parece que você precisará passá-lo separadamente
Vladimir
Muito obrigado. onde você encontrou esses dados? Eu sempre aprecio a referência para que talvez eu a encontre da próxima vez.
bearMountain
1
@bearMountain, você pode conferir este link: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/… por exemplo
Vladimir
3
Você não precisa passá-lo separadamente. Se você está passando algum NSObject, você pode apenas dizer [button.layer setValue:yourObject forKey:@"anyKey"];e, em seguida, no método, marque (objectClass *)[button.layer valueForKey:@"anyKey"];É como uma versão mais gratuita de.tag
Albert Renshaw
64

Para passar parâmetros personalizados junto com o botão, basta SUBCLASS UIButton .

(O ASR está ativado, não há liberações no código.)

Este é o myButton.h

#import <UIKit/UIKit.h>

@interface myButton : UIButton {
    id userData;
}

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

@end

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

Recebendo a função:

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

Se você precisar que eu seja mais específico em qualquer parte do código acima, fique à vontade para perguntar sobre isso nos comentários.

Yuriy Polezhayev
fonte
Eu acho que essa é a melhor maneira
Çağatay Gürtürk
Solução mais elegante.
Victor C.
2
Ótima resposta ... muito personalizável. Exatamente o que eu estava procurando. MOLODETS! :)
denikov
Obrigado pelo feedback :) Glad, que é útil para alguém :)
Yuriy Polezhayev
1
Solução simples e útil. Deixe-me acrescentar que isso é semelhante à tagpropriedade nas visualizações do Android . Eles podem armazenar qualquer objeto. E você também pode armazenar objetos por chave, como em um Dicionário / Mapa.
Ferran Maylinch 04/12/2015
19

A Target-Action permite três formas diferentes de seletor de ações:

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

Precisa de mais do que apenas um (int) via .tag? Use KVC!

Você pode transmitir quaisquer dados que desejar através do próprio objeto de botão (acessando o ditado de KeyAvalue do CALayers).


Defina seu destino assim (com o ":")

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

Adicione seus dados ao próprio botão (bem como o .layerdo botão que está) assim:

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!

Então, quando o botão é pressionado, você pode verificá-lo assim:

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

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

}
Albert Renshaw
fonte
não há método no UIButton.layer para definir ou adicionar um objeto. De onde você tira essa solução?
mAc 7/17
1
Um UIButton é um tipo de UIView, todos os UIViews possuem a propriedade .ayer da CALayer. CALayer contém comportamentos NSDictionary. Observe que este é o Objective-C e não o Swift, esse pode ser o problema? Aqui estão Docs da Apple sobre key-value CALayer codificação: developer.apple.com/library/content/documentation/Cocoa/...
Albert Renshaw
1
Esta deve ser a resposta no topo. De longe, a maneira mais fácil de fazer isso !. obrigado!
Danielrosero 18/09/18
1
Deve ser aceito answer.Its maneira muito mais fácil e inteligente.
Emon
1
Ótima resposta! Gostaria de saber isso há muito tempo.
John
7

Eu fiz uma solução baseada em parte pelas informações acima. Acabei de definir o titlelabel.text como a string que desejo passar e defina o titlelabel.hidden = YES

Como isso :

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

Dessa forma, não há necessidade de herança ou categoria e não há vazamento de memória

Abhinav Singh
fonte
5

Eu estava criando vários botões para cada número de telefone em uma matriz, de modo que cada botão precisava de um número de telefone diferente para ligar. Usei a função setTag, pois estava criando vários botões em um loop 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];
}

Então, na minha chamada: method, usei o mesmo para loop e uma instrução if para escolher o número de telefone correto:

- (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
fonte
você pode melhorar 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 existem várias maneiras mencionadas aqui para a solução, exceto o recurso de categoria.

Use o recurso de categoria para estender o elemento definido (interno) ao seu elemento personalizável.

Por exemplo (ex):

@interface UIButton (myData)

@property (strong, nonatomic) id btnData;

@end

na sua visualização Controller.m

 #import "UIButton+myAppLists.h"

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

Manipulador de eventos:

-(void)buttonClicked : (UIButton*)sender{
    NSLog(@"my Data %@", sender. btnData);
}
Kumar KL
fonte
Você não pode realmente adicionar propriedades em categorias.
HotFudgeSunday
2

Você pode substituir a ação de destino por um fechamento (bloco no Objective-C) adicionando um wrapper auxiliar de fechamento (ClosureSleeve) e adicionando-o como um objeto associado ao controle para que ele seja retido. Dessa forma, você pode passar quaisquer parâmetros.

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ý
fonte
Método acima ( self.switchToNewsDetails(parameter: i)) disparar 2 vezes, por me ajudar
Nagendar
O código funciona para mim. Testado em um novo projeto de aplicativo único de exibição pastebin.com/tPaqetMb . Portanto, o problema provavelmente está no seu código, como ligar button.addAction()duas vezes. Adicione um ponto de interrupção button.addActione também na primeira linha dentro do fechamento e tente depurar você mesmo.
Marián Černý
2

Existe uma outra maneira, na qual você pode obter o indexPath da célula onde seu botão foi pressionado:

usando o seletor de ação usual como:

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

e depois em yourFunction:

   - (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 você obtenha um indexPath, ele se torna muito mais simples.

Anton Lisitsyn
fonte
1

Veja meu comentário acima e acredito que você precise usar o NSInvocation quando houver mais de um parâmetro

mais informações sobre o NSInvocation aqui

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

Aaron Saunders
fonte
Na verdade, não quero passar vários parâmetros, só quero um número inteiro.
Pierre Espenan
na verdade, existe o método performSelector: withObject: withObject: que permite chamar seletores com 2 parâmetros. Mas, para mais, você precisa do NSInvocation
Vladimir
1

Isso corrigiu o meu problema, mas travava, a menos que eu mudasse

action:@selector(switchToNewsDetails:event:)                 

para

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

Subclassifiquei UIButton no CustomButton e adiciono uma propriedade onde armazeno meus dados. Por isso, chamo o método: (CustomButton *) remetente e, no método, só leio meus dados sender.myproperty.

Exemplo CustomButton:

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

Ação do método:

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

Atribuir ação

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

Se você quiser apenas alterar o texto do leftBarButtonItem mostrado pelo controlador de navegação junto com a nova visualização, você pode alterar o título da visualização atual antes de chamar pushViewController para o texto desejado e restaurá-lo no retorno de chamada viewHasDisappered para exibições futuras de a visão atual.

Essa abordagem mantém a funcionalidade (popViewController) e a aparência da seta mostrada intactas.

Funciona para nós pelo menos com o iOS 12, construído com o Xcode 10.1 ...

Bubu
fonte
Não acho que esta resposta esteja relacionada à pergunta.
Pierre Espenan