Anexe o parâmetro à ação button.addTarget em Swift


102

Estou tentando passar um parâmetro extra para a ação buttonClicked, mas não consigo descobrir qual deve ser a sintaxe em Swift.

button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)

Qualquer método meu buttonClicked:

func buttonClicked(sender:UIButton)
{
    println("hello")
}

Alguém tem alguma ideia?

Obrigado pela ajuda.


1
Consulte este link para obter uma resposta detalhada e melhores práticas
folha de

A pergunta não tem uma boa resposta porque já descreve uma arquitetura problemática. Você não precisa de um parâmetro extra. A necessidade de um parâmetro adicional é um problema em sua arquitetura.
Sulthan

WARNING TO INTERNET: please check out my answer at the bottom
Sr. Heelis,

Respostas:


170

Você não pode passar parâmetros personalizados em addTarget:. Uma alternativa é definir a tagpropriedade do botão e trabalhar com base na tag.

button.tag = 5
button.addTarget(self, action: "buttonClicked:", 
    forControlEvents: UIControlEvents.TouchUpInside)

Ou para Swift 2.2 e superior:

button.tag = 5
button.addTarget(self,action:#selector(buttonClicked),
    forControlEvents:.TouchUpInside)

Agora faça a lógica baseada na tagpropriedade

@objc func buttonClicked(sender:UIButton)
{
    if(sender.tag == 5){

        var abc = "argOne" //Do something for tag 5
    }
    print("hello")
}

4
Oi, Se eu quiser obter indexPath ou célula de botão em tableView, é possível?
NKurapati

1
Aqui está uma dica: não torne o método privado.
OhadM de

2
Parece que apenas o tipo pode ser um Int. Nada mais.
assustador

2
e se eu quiser passar linha e seção juntas?
Yestay Muratov

3
só para você saber, apesar de 118 votos positivos (e contando), esta é a maneira errada de fazer isso
Sr. Heelis

79

Se você deseja enviar parâmetros adicionais para o método buttonClicked, por exemplo, um indexPath ou urlString, você pode criar uma subclasse de UIButton:

class SubclassedUIButton: UIButton {
    var indexPath: Int?
    var urlString: String?
}

Certifique-se de alterar a classe do botão no inspetor de identidade para subclassedUIButton. Você pode acessar os parâmetros dentro do método buttonClicked usando sender.indexPathou sender.urlString.

Nota: Se o seu botão estiver dentro de uma célula, você pode definir o valor desses parâmetros adicionais no método cellForRowAtIndexPath (onde o botão é criado).


5
Essa foi a única maneira que funcionou para mim. Mas estou curioso, isso é anti-padrão ou não?
assustador

1
Esta é a melhor maneira, eu acho
Duy Hoang

29

Agradeço a todos que dizem usar tags, mas você realmente precisa estender a classe UIButton e simplesmente adicionar o objeto lá.

As tags são uma maneira impossível de contornar isso. Estenda o UIButton assim (no Swift 4)

import UIKit
class PassableUIButton: UIButton{
    var params: Dictionary<String, Any>
    override init(frame: CGRect) {
        self.params = [:]
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        self.params = [:]
        super.init(coder: aDecoder)
    }
}

então sua chamada pode ser chamada (NOTE OS dois pontos ":" em Selector(("webButtonTouched:")))

let webButton = PassableUIButton(frame: CGRect(x:310, y:40, width:40, height:40))
webButton.setTitle("Visit",for: .normal)
webButton.addTarget(self, action: #selector(YourViewController.webButtonTouched(_:)), for:.touchUpInside)
webButton.params["myvalue"] = "bob"

então finalmente pegue tudo aqui

@IBAction func webButtonTouched(_ sender: PassableUIButton) {
    print(sender.params["myvalue"] ?? "")
}

Você faz isso uma vez e usa em todo o seu projeto (você pode até fazer a classe filha ter um "objeto" genérico e colocar o que quiser no botão!). Ou use o exemplo acima para colocar um número inesgotável de parâmetros de chave / string no botão. Muito útil para incluir coisas como urls, metodologia de mensagem de confirmação, etc.

Como um aparte, é importante que a SOcomunidade perceba que existe toda uma geração de práticas ruins sendo cortadas e coladas na internet por um número alarmante de programadores que não entendem / não foram ensinados / erraram o ponto de O conceito deobject extensions


10

Para Swift 3.0, você pode usar o seguinte

button.addTarget(self, action: #selector(YourViewController.YourMethodName(_:)), for:.touchUpInside)

func YourMethodName(_ sender : UIButton) {
    print(sender.tag)

}

9

Swift 4.2

Resultado:

testButton.on(.touchUpInside) { (sender, event) in
    // You can use any reference initialized before the code block here
    // You can access self by adding [weak self] before (sender, event)
    // You can then either make self strong by using a guard statement or use a optional operator (?)
    print("user did press test button")
}

No arquivo UIButton+Events.swift, criei um método de extensão para UIButtonque vincula um UIControl.Eventa um manipulador de conclusão chamado EventHandler:

import UIKit

fileprivate var bindedEvents: [UIButton:EventBinder] = [:]

fileprivate class EventBinder {

    let event: UIControl.Event
    let button: UIButton
    let handler: UIButton.EventHandler
    let selector: Selector

    required init(
        _ event: UIControl.Event,
        on button: UIButton,
        withHandler handler: @escaping UIButton.EventHandler
    ) {
        self.event = event
        self.button = button
        self.handler = handler
        self.selector = #selector(performEvent(on:ofType:))
        button.addTarget(self, action: self.selector, for: event)
    }

    deinit {
        button.removeTarget(self, action: selector, for: event)
        if let index = bindedEvents.index(forKey: button) {
            bindedEvents.remove(at: index)
        }
    }
}

private extension EventBinder {

    @objc func performEvent(on sender: UIButton, ofType event: UIControl.Event) {
        handler(sender, event)
    }
}

extension UIButton {

    typealias EventHandler = (UIButton, UIControl.Event) -> Void

    func on(_ event: UIControl.Event, handler: @escaping EventHandler) {
        bindedEvents[self] = EventBinder(event, on: self, withHandler: handler)
    }
}

O motivo pelo qual usei uma classe personalizada para vincular o evento é para poder descartar a referência posteriormente, quando o botão for desintializado. Isso evitará que ocorra um possível vazamento de memória. Isso não foi possível dentro de UIButtonsua extensão, pois não tenho permissão para implementar uma propriedade nem o deinitmétodo.


2

Se você tem uma série de botões como eu, pode tentar algo assim

var buttonTags:[Int:String]? // can be [Int:Any]
let myArray = [0:"a",1:"b"]
for (index,value) in myArray {

     let button = // Create a button

     buttonTags?[index] = myArray[index]
     button.tag = index
     button.addTarget(self, action: #selector(buttonAction(_:)), for: .touchDown)

}
@objc func buttonAction(_ sender:UIButton) {

    let myString = buttonTags[sender.tag]

}

2

Código Swift 4.0 (lá vamos nós de novo)

A ação chamada deve ser marcada assim porque essa é a sintaxe da função swift para exportar funções para a linguagem c objetiva.

@objc func deleteAction(sender: UIButton) {
}

crie algum botão funcional:

let deleteButton = UIButton(type: .roundedRect)
deleteButton.setTitle("Delete", for: [])
deleteButton.addTarget(self, action: #selector( 
MyController.deleteAction(sender:)), for: .touchUpInside)

Obrigado por compartilhar. Isso funciona, mas marcá-lo com @objc não parece intuitivo. Você pode explicar o motivo disso?
rawbee

@rawbee Concordo que isso é contra-intuitivo. Esta pergunta tem mais antecedentes: stackoverflow.com/questions/44390378/…
Mikrasya

1

Código Swift 5.0

Eu uso theButton.tag, mas se eu tiver bastante tipo de opção, é um caso de switch muito longo.

theButton.addTarget(self, action: #selector(theFunc), for: .touchUpInside) 
theButton.frame.name = "myParameter"

.

@objc func theFunc(sender:UIButton){ 
print(sender.frame.name)
}

0

Para Swift 2.X e superior

button.addTarget(self,action:#selector(YourControllerName.buttonClicked(_:)),
                         forControlEvents:.TouchUpInside)

0

Código Swift 3.0

self.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector:#selector(fetchAutocompletePlaces(timer:)), userInfo:[textView.text], repeats: true)

func fetchAutocompletePlaces(timer : Timer) {

  let keyword = timer.userInfo
}

Você pode enviar o valor em 'userinfo' e usá-lo como parâmetro na função.


0

Este é mais um comentário importante. Compartilhamento de referências de sytanx que são aceitáveis ​​fora da caixa. Para soluções de hack, olhe para outras respostas.

De acordo com os documentos da Apple, as Definições de Método de Ação devem ser uma dessas três. Qualquer outra coisa não é aceita.

@IBAction func doSomething()
@IBAction func doSomething(sender: UIButton)
@IBAction func doSomething(sender: UIButton, forEvent event: UIEvent)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.