Dołącz parametr do akcji button.addTarget w Swift

102

Próbuję przekazać dodatkowy parametr do akcji buttonClicked, ale nie mogę ustalić, jaka powinna być składnia w języku Swift.

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

Dowolna metoda buttonClicked:

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

Czy ktoś ma jakieś pomysły?

Dzięki za pomoc.

iphaaw
źródło
1
Skorzystaj z tego łącza, aby uzyskać szczegółową odpowiedź i najlepsze praktyki
arkuszowy
To pytanie nie ma dobrej odpowiedzi, ponieważ opisuje już problematyczną architekturę. Nie potrzebujesz dodatkowego parametru. Potrzeba dodatkowego parametru jest problemem w Twojej architekturze.
Sulthan
WARNING TO INTERNET: please check out my answer at the bottom
Pan Heelis,

Odpowiedzi:

170

Nie możesz przekazać parametrów niestandardowych w addTarget:.Jedną alternatywą jest ustawienie tagwłaściwości przycisku i wykonanie pracy w oparciu o tag.

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

Lub dla Swift 2.2 i nowszych:

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

Teraz wykonaj logikę opartą na tagwłasności

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

        var abc = "argOne" //Do something for tag 5
    }
    print("hello")
}
kodester
źródło
4
Cześć, jeśli chcę uzyskać indexPath lub komórkę przycisku w tableView, czy jest to możliwe?
NKurapati
1
Oto wskazówka: nie ustawiaj tej metody jako prywatnej.
OhadM,
2
Wygląda na to, że jego typem może być tylko plik Int. Nic więcej.
scaryguy
2
co jeśli chcę przekazać wiersz i sekcję razem?
Yestay Muratov
3
tak, żebyś wiedział, pomimo 118 głosów za (i wciąż rosnących), jest to zły sposób na zrobienie tego
pan Heelis,
79

Jeśli chcesz wysłać dodatkowe parametry do metody buttonClicked, na przykład indexPath lub urlString, możesz podklasować UIButton:

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

Upewnij się, że zmienisz klasę przycisku w inspektorze tożsamości na podklasęUIButton. Możesz uzyskać dostęp do parametrów wewnątrz metody buttonClicked za pomocą sender.indexPathlub sender.urlString.

Uwaga: Jeśli twój przycisk znajduje się wewnątrz komórki, możesz ustawić wartość tych dodatkowych parametrów w metodzie cellForRowAtIndexPath (gdzie przycisk jest tworzony).

uclagamer
źródło
5
To był jedyny sposób, który zadziałał dla mnie. Ale jestem ciekawy, czy to anty-wzór, czy nie?
scaryguy
1
Myślę, że to najlepszy sposób
Duy Hoang
29

Doceniam wszystkich, którzy mówią używaj tagów, ale tak naprawdę musisz rozszerzyć klasę UIButton i po prostu dodać tam obiekt.

Tagi są beznadziejnym sposobem obejścia tego. Rozszerz UIButton w ten sposób (w 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)
    }
}

wtedy może to być połączenie (UWAGA dwukropek „:” w 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"

w końcu złap to wszystko tutaj

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

Robisz to raz i używasz go w całym projekcie (możesz nawet sprawić, by klasa podrzędna miała ogólny „obiekt” i wstawić cokolwiek chcesz do przycisku!). Lub skorzystaj z powyższego przykładu, aby umieścić niewyczerpaną liczbę parametrów klucza / ciągu znaków w przycisku. Naprawdę przydatne do uwzględniania takich rzeczy, jak adresy URL, metodologia potwierdzania wiadomości itp.

SONawiasem mówiąc, ważne jest, aby społeczność zdała sobie z tego sprawę, że przez alarmującą liczbę programistów, którzy nie rozumieją / nie zostali nauczeni / przegapili sens pojęcieobject extensions

Panie Heelis
źródło
10

W przypadku Swift 3.0 możesz użyć następujących

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

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

}
MK_iOS
źródło
9

Swift 4.2

Wynik:

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

W pliku UIButton+Events.swiftstworzyłem metodę rozszerzenia, UIButtonktóra wiąże a UIControl.Eventz programem obsługi zakończenia o nazwie 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)
    }
}

Powodem, dla którego użyłem niestandardowej klasy do powiązania zdarzenia, jest możliwość usunięcia odwołania później, gdy przycisk jest deintializowany. Zapobiegnie to ewentualnemu wyciekowi pamięci. Nie było to możliwe w ramach UIButtonjego rozszerzenia, ponieważ nie mogę zaimplementować właściwości ani deinitmetody.

Lloyd Keijzer
źródło
2

Jeśli masz pętlę przycisków jak ja, możesz spróbować czegoś takiego

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]

}
brahimm
źródło
2

Kod Swift 4.0 (znowu zaczynamy)

Wywołana akcja powinna być oznaczona w ten sposób, ponieważ jest to składnia funkcji szybkiej do eksportowania funkcji do obiektywnego języka c.

@objc func deleteAction(sender: UIButton) {
}

utwórz działający przycisk:

let deleteButton = UIButton(type: .roundedRect)
deleteButton.setTitle("Delete", for: [])
deleteButton.addTarget(self, action: #selector( 
MyController.deleteAction(sender:)), for: .touchUpInside)
Karsten
źródło
Dzięki za udostępnienie. To działa, ale oznaczanie go @objc wydaje się nieintuicyjne. Czy możesz wyjaśnić powód tego?
rawbee
@rawbee Zgadzam się, że jest to sprzeczne z intuicją. To pytanie ma więcej tła: stackoverflow.com/questions/44390378/…
Mikrasya
1

Kod Swift 5.0

Używam theButton.tag, ale jeśli mam dużo opcji, jest to bardzo długa obudowa przełącznika.

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

.

@objc func theFunc(sender:UIButton){ 
print(sender.frame.name)
}
gorimali
źródło
0

Dla Swift 2.X i nowszych

button.addTarget(self,action:#selector(YourControllerName.buttonClicked(_:)),
                         forControlEvents:.TouchUpInside)
Dheeraj D
źródło
0

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

Możesz wysłać wartość w „informacje o użytkowniku” i użyć jej jako parametru w funkcji.

krish
źródło
0

To jest bardziej ważny komentarz. Udostępnianie referencji sytanx, które jest dopuszczalne po wyjęciu z pudełka. W przypadku rozwiązań hakerskich spójrz na inne odpowiedzi.

Zgodnie z dokumentacją Apple, definicje metod działania muszą być jedną z tych trzech. Wszystko inne jest niedopuszczalne.

@IBAction func doSomething()
@IBAction func doSomething(sender: UIButton)
@IBAction func doSomething(sender: UIButton, forEvent event: UIEvent)
kochanie
źródło