Übergeben von Argumenten an den Selektor in Swift

73

Ich füge programmgesteuert einen UITapGestureRecognizer zu einer meiner Ansichten hinzu:

let gesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(modelObj:myModelObj)))

self.imageView.addGestureRecognizer(gesture)

func handleTap(modelObj: Model) {
  // Doing stuff with model object here
}

Das erste Problem, auf das ich gestoßen bin, war "Argument von '#selector' bezieht sich nicht auf eine '@Objc'-Methode, -Eigenschaft oder -Initialisierer.

Cool, also habe ich @objc zur handleTap-Signatur hinzugefügt:

@objc func handleTap(modelObj: Model) {
  // Doing stuff with model object here
}

Jetzt erhalte ich die Fehlermeldung "Methode kann nicht als @objc markiert werden, da der Typ des Parameters in Objective-C nicht dargestellt werden kann.

Es ist nur ein Bild der Karte eines Gebäudes, wobei einige Pin-Bilder den Standort von Sonderzielen angeben. Wenn der Benutzer auf einen dieser Pins tippt, möchte ich wissen, auf welchen Punkt von Interesse er getippt hat, und ich habe ein Modellobjekt, das diese Punkte von Interesse beschreibt. Ich verwende dieses Modellobjekt, um dem Pin-Bild die Koordinaten auf der Karte zu geben, sodass ich dachte, es wäre für mich einfach gewesen, das Objekt einfach an den Gestenhandler zu senden.

Mike
quelle
1
Sie können solche Werte nicht an einen Selektor übergeben. Warum können Sie diesen Wert nicht in einer Instanzvariablen behalten und über die Auswahlmethode darauf zugreifen?
Midhun MP
4
Mögliches Duplikat von stackoverflow.com/questions/35635595/… .
Martin R

Antworten:

93

Es sieht so aus, als ob Sie ein paar Dinge falsch verstehen.

Bei Verwendung von Ziel / Aktion muss die Funktionssignatur eine bestimmte Form haben…

func doSomething(sender: Any)

oder

func doSomething(sender: Any, forEvent event: UIEvent)

wo…

Der senderParameter ist das Steuerobjekt, das die Aktionsnachricht sendet.

In Ihrem Fall ist der Absender der UITapGestureRecognizer

Auch #selector()sollte die func Signatur enthalten, und NICHT übergebenen Parameter umfassen. So für…

func handleTap(sender: UIGestureRecognizer) {

}

du solltest haben…

let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))

Angenommen, die Funktion und die Geste befinden sich in einem Ansichtscontroller, von dem modelObjeine Eigenschaft / ein Ivar ist, müssen Sie sie nicht mit dem Gestenerkenner übergeben. Sie können einfach darauf verweisenhandleTap

Ashley Mills
quelle
1
Der Abschnitt " Zielaktionsmechanismus" in der UIControl-Dokumentation für Swift ist ebenfalls eine gute Lektüre, in der die drei Formen aufgeführt sind, die Aktionsmethoden annehmen können.
Toraritte
Um diese gute Antwort zu ergänzen, verfügen bestimmte Objekttypen wie Timer über userInfo: Any?Parameter, die in Verbindung mit dieser Lösung verwendet werden können, um Modellobjekte mit dem Absender zu übergeben.
Justin Vallely
32

Schritt 1: Erstellen Sie das benutzerdefinierte Objekt des Absenders.

Schritt 2: Fügen Sie Eigenschaften hinzu, die Sie ändern möchten, und zwar als benutzerdefiniertes Objekt des Absenders

Schritt 3: Geben Sie den Absender in der Empfangsfunktion in ein benutzerdefiniertes Objekt ein und greifen Sie auf diese Eigenschaften zu

Zum Beispiel: Klicken Sie auf die Schaltfläche, wenn Sie die Zeichenfolge oder ein benutzerdefiniertes Objekt senden möchten

Schritt 1: erstellen

class CustomButton : UIButton {

    var name : String = ""
    var customObject : Any? = nil
    var customObject2 : Any? = nil

    convenience init(name: String, object: Any) {
        self.init()
        self.name = name
        self.customObject = object
    }
}

Schritt 2-a: Legen Sie die benutzerdefinierte Klasse auch im Storyboard fest

Geben Sie hier die Bildbeschreibung ein

Schritt 2-b: Erstellen Sie das IBOutlet dieser Schaltfläche mit einer benutzerdefinierten Klasse wie folgt

@IBOutlet weak var btnFullRemote: CustomButton!

Schritt 3: Fügen Sie Eigenschaften hinzu, die Sie ändern möchten, und zwar als benutzerdefiniertes Objekt des Absenders

btnFullRemote.name = "Nik"
btnFullRemote.customObject = customObject
btnFullRemote.customObject2 = customObject2
btnFullRemote.addTarget(self, action: #selector(self.btnFullRemote(_:)), for: .touchUpInside)

Schritt 4: Geben Sie den Absender in der Empfangsfunktion in ein benutzerdefiniertes Objekt ein und greifen Sie auf diese Eigenschaften zu

@objc public func btnFullRemote(_ sender: Any) {

var name : String = (sender as! CustomButton).name as? String

var customObject : customObject = (sender as! CustomButton).customObject as? customObject

var customObject2 : customObject2 = (sender as! CustomButton).customObject2 as? customObject2

}
Ninad Kambli
quelle
2
Clevere Antwort, die eine gute Lösung bietet (anstatt zu sagen, dass es unmöglich ist, wie die meisten Antworten, die ich zu diesem Thema gefunden habe) ... Versuchte diesen Ansatz und kann bestätigen, dass er funktioniert
Josh Wolff
1
Benutzerdefinierter Objekt-Wrapper für alles, was Sie brauchen! So klug. Vielen Dank.
Kyle Beard
7

Swift 5.0 iOS 13

Ich stimme einer großartigen Antwort von Ninad zu . Hier sind meine 2 Cent, die gleiche und doch andere Technik; eine minimale Version.

Erstellen Sie eine benutzerdefinierte Klasse, werfen Sie eine Aufzählung, um den Code so wartbar wie möglich zu halten.

enum Vs: String {
  case pulse = "pulse"
  case precision = "precision"
} 

class customTap: UITapGestureRecognizer {
  var cutomTag: String?
}

Verwenden Sie diese Option, und stellen Sie sicher, dass Sie die benutzerdefinierte Variable in den Balgin setzen. Wenn Sie hier ein einfaches Etikett verwenden, beachten Sie die letzte Zeile. Wichtige Etiketten sind normalerweise nicht interaktiv.

let precisionTap = customTap(target: self, action: #selector(VC.actionB(sender:)))
precisionTap.customTag = Vs.precision.rawValue
precisionLabel.addGestureRecognizer(precisionTap)
precisionLabel.isUserInteractionEnabled = true

Und richten Sie die Aktion damit ein. Beachten Sie, dass ich die reine Aufzählung verwenden wollte, diese jedoch von Objective C nicht unterstützt wird. Daher verwenden wir einen Basistyp, in diesem Fall String.

@objc func actionB(sender: Any) {
// important to cast your sender to your cuatom class so you can extract your special setting.
  let tag = customTag as? customTap
  switch tag?.sender {
    case Vs.pulse.rawValue:
      // code
    case Vs.precision.rawValue:
      // code
    default:
      break
    }
}

Und da hast du es.

user3069232
quelle
Genial. das funktioniert. Aber ich denke, es sollte korrigiert werden, dass @objc func actionB (Absender: Beliebig) {let tap = Absender als? customTap Schalter Hahn .customTag {...?
madu
2

Erstellen Sie einfach eine benutzerdefinierte Klasse von UITapGestureRecognizer =>

import UIKit

class OtherUserProfileTapGestureRecognizer: UITapGestureRecognizer {

    let userModel: OtherUserModel    
    init(target: AnyObject, action: Selector, userModel: OtherUserModel) {
        self.userModel = userModel
        super.init(target: target, action: action)
    }
}

Und dann erstellen Sie die UIImageView-Erweiterung =>

import UIKit

    extension UIImageView {
    
        func gotoOtherUserProfile(otherUserModel: OtherUserModel) {
            isUserInteractionEnabled = true
            let gestureRecognizer = OtherUserProfileTapGestureRecognizer(target: self, action: #selector(self.didTapOtherUserImage(_:)), otherUserModel: otherUserModel)
            addGestureRecognizer(gestureRecognizer)
        }
        
        @objc internal func didTapOtherUserImage(_ recognizer: OtherUserProfileTapGestureRecognizer) {
            Router.shared.gotoOtherUserProfile(otherUserModel: recognizer.otherUserModel)
        }
    }

Verwenden Sie es jetzt wie =>

self.userImageView.gotoOtherUserProfile(otherUserModel: OtherUserModel)
Rajan Singh
quelle
-1

Das mag eine schreckliche Praxis sein, aber ich füge einfach hinzu, was ich wiederherstellen möchte

button.restorationIdentifier = urlString

und

@objc func openRelatedFact(_ sender: Any) {
        if let button = sender as? UIButton, let stringURL = factButton.restorationIdentifier, let url = URL(string: stringURL) {
            if UIApplication.shared.canOpenURL(url) {
                UIApplication.shared.open(url, options: [:])
            }
        }

    }
Chucky
quelle
Schreckliche Übung… ja. Schrecklich erfinderisch, das auch! Kudo!
SpacyRicochet
-1
cell.btn.tag = indexPath.row //setting tag
cell.btn.addTarget(self, action: #selector(showAlert(_ :)), for: .touchUpInside)

@objc func showAlert(_ sender: UIButton){
  print("sender.tag is : \(sender.tag)")// getting tag's value
}
Amanpreet Singh
quelle
4
Diese Frage wurde vor einiger Zeit beantwortet und hat bereits eine akzeptierte Antwort. Dies bedeutet, dass jede zusätzliche Antwort jedem Leser dieser Frage eine neue Perspektive bieten sollte. Daher würde ich Ihnen raten, Ihrer Antwort etwas mehr Kontext hinzuzufügen und zu beschreiben, warum dies eine einzigartige Möglichkeit bietet, das Problem der Frage zu lösen. Weitere Informationen zur Beantwortung einer Frage finden Sie hier: stackoverflow.com/help/how-to-answer
BeWu