passing parameters to a Selector in Swift

22,545

Solution 1

There are two distinct problems here:

  1. The syntax for the selector is wrong; the : doesn't mark the beginning of a parameters part, it merely marks that this function takes parameters at all. So the tap recognizer should be initialized as UITapGestureRecognizer(target: self, action: "labelTicked:"). Easy fix. That is the good news.

  2. The bad news: even with perfect syntax it will not work the way you set it up here. Your tap recognizer cannot pass a WeeklyAssignment object as a parameter. In fact, it cannot pass any custom parameter at all. At least, not like that.

What it can pass, however, is its sender (which is usually the view the gesture recognizer is attached to). You can grab it by changing your method to

func labelTicked(sender: AnyObject) {

(note that AnyObject may be declared as a more specific type if you know exactly what to expect.)

Going through the sender, you could now theoretically infer which label it is that has been tapped, which data entity that labels corresponds to, and which state the checked property of that entity is in. I think this would become very convoluted very quickly.

Seemingly straightforward things becoming convoluted is usually a good sign that we should take a step back and look for a better solution.

I'd suggest dropping the whole GestureRecognizer approach at this point, and instead exploit the fact that each cell in a table view already comes with its own "tap recongizing" functionality out of the box: the didSelectRowAtIndexPath: method of the table view's delegate. There, you can easily use the supplied NSIndexPath to retrieve the corresponding model entity from the data source to read and modify its parameters as you see fit. Via cellForRowAtIndexPath: you can then get a reference to the correct cell and change its contents accordingly.


Update for Swift 3:

As the Swift language evolves and using String-based selectors (e.g. "labelTicked:") is now flagged as deprecated, I think it's appropriate to provide a small update to the answer.

Using more modern syntax, you could declare your function like this:

@objc func labelTicked(withSender sender: AnyObject) {

and initialize your gesture recognizer like this, using #selector:

UITapGestureRecognizer(target: self, action: #selector(labelTicked(withSender:)))

Solution 2

The correct selector for that function is labelTicked:. Furthermore, you can use a string directly as a selector. Thus:

let gestureRecognizer = UITapGestureRecognizer(target: self, action: "labelTicked:")

But you can't arrange to pass an arbitrary object along to the method when the recognizer fires. A better way to do this is to create a subclass of UITableViewCell. Let's call it AssignmentCell. Give the subclass an assignment property, and move the labelTicked: method to AssignmentCell.

If you've designed the cell in your storyboard, you can add a tap recognizer to the label right in the storyboard, and wire the recognizer to the cell's labelTicked: method in the storyboard.

In your table view data source's tableView(_:cellForRowAtIndexPath:), after you've dequeued the cell, set its assignment property to the assignment for that row.

Share:
22,545
Cyrus Price
Author by

Cyrus Price

Updated on July 09, 2022

Comments

  • Cyrus Price
    Cyrus Price almost 2 years

    I am building an app for keeping track of reading assignments for a university course. Each ReadingAssignment has included a Bool value that indicates if the reader has finished reading the assignment. The ReadingAssignments are collected into WeeklyAssignment arrays. I want to have the user be able to touch a label and have a checkmark appear and show the assignment as completed. I would like this touch to also update the .checked property to true so I can persist the data. So, I am trying to have the gestureRecognizer call the labelTicked() method. This works and prints to the console. However, when I try to pass in the assignment parameter, it compiles, but crashes on the touch with an "unrecognized selector" error. I have read every topic i can find here, and haven't found the solution. They all say ":" signifies a Selector with parameters, but still no go. Can you see what I am doing wrong?

    func configureCheckmark(cell: UITableViewCell, withWeeklyAssignment assignment: WeeklyAssignment) {
    
        let checkLabel = cell.viewWithTag(1002) as! UILabel
        checkLabel.userInteractionEnabled = true
        let gestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("labelTicked:assignment"))
        checkLabel.addGestureRecognizer(gestureRecognizer)
    
    }
    
    @objc func labelTicked(assignment: WeeklyAssignment) {
    
        assignment.toggleCheckmark()
    
            if assignment.checked {
                label.text = "✔︎"
            } else {
                label.text = ""
            }
    }
    

    I would also love to pass in the UILabel checkLabel so I can update it in the labelTicked() method. Thanks for your help.

  • Cyrus Price
    Cyrus Price over 8 years
    Thanks for the comment Rob. I am still learning programming, so I have a basic understanding of what you are saying (i think). So, can i have my WeeklyAssignment class inherit from UITableViewCell...you know what, I think I understand what you are saying, but not how to do what you are describing.
  • Cyrus Price
    Cyrus Price over 8 years
    That was originally the way I had it set up. But I am using the didSelectRowAtIndexPath: to perform a segue to take them to the view that shows the details of the reading assignment. Is it possible (like on a sprite) to divide up the cell, so a touch on the leading 1/4 of the cell performs a checkmark method and a touch on the remainder of the cell would perform the segue?