sevensevens
sevensevens

Reputation: 1750

Containing class or parent class responding to addtarget function

I'm having some trouble understanding how addtarget works. I've replicated my issue in the swift class below. The button appears correctly on the screen, but when I press it, the PopupMenuViewController.pressed function is called not the handlerClass.pressed function.
Why?

import Foundation
import UIKit
import CoreLocation

class PopupMenuViewController : UIViewController
{
   class handlerClass
   {
     func pressed(sender: UIButton!) {
        /// **IT SEEMS LIKE THE CALL FROM ADDTARGET SHOULD GO HERE...**
        var alertView = UIAlertView();
        alertView.addButtonWithTitle("Ok");
        alertView.title = "title";
        alertView.message = "message";
        alertView.show();
     }
  }

  func pressed(sender: UIButton!) {
     // **BUT INSTEAD IT ENDS UP HERE!!!!**
     var alertView = UIAlertView();
     alertView.addButtonWithTitle("Ok");
     alertView.title = "title";
     alertView.message = "message";
     alertView.show();
  }

  override func viewDidLoad()
  {
     super.viewDidLoad()
     let hc = handlerClass()  // make me a member variable to fix this problem

     let button1 = UIButton()
     button1.frame = CGRectMake(10, 400, 100, 50)
    button1.backgroundColor =      UIColor.lightGrayColor().colorWithAlphaComponent(0.9)
     button1.layer.cornerRadius = 10.0
     button1.setTitle("Button ", forState: UIControlState.Normal)
     // ** THE OFFENDING ADD TARGE CALL **
     button1.addTarget(hc, action: "pressed:", forControlEvents: .TouchUpInside)
     self.view.addSubview(button1)

  }
}

FIX: Thanks Carl for pointing this out. let hc = handlerClass() should be promoted to a member variable

Upvotes: 0

Views: 470

Answers (3)

Chris Ricca
Chris Ricca

Reputation: 61

I think I know what is causing this weird behavior. First, your let hc = handlerClass() variable is not retained outside of the scope of viewDidLoad. In other words, after viewDidLoad finishes running, it effectively no longer exists.

Whenever you use the target action pattern, you need to guarantee that when the event is triggered, the target is still around somewhere.

Update based on another answer: Because the hc variable is released but the time the action is triggered, the object that ends up being called is the next object up the responder chain that responds to pressed:.

One solution to this is to add a var handler: HandlerClass? to your parent class, and store the handler when you create it.

However, when I did this in a local test, I encountered another strange error that the instance (of the handler class) didn't respond to pressed:. It wasn't until I made the HandlerClass descend from NSObject that it worked as expected. Here's the working example I ended up with:

import UIKit

class ViewController: UIViewController {
    var handler: HandlerClass?

    class HandlerClass : NSObject
    {
        func pressed(sender: UIButton!) {
            /// **THE CALL FROM ADD TARGET ENDS UP HERE**
            let alertView = UIAlertView();
            alertView.addButtonWithTitle("Ok");
            alertView.title = "HandlerClass";
            alertView.message = "HandlerClass";
            alertView.show();
        }
    }

    func pressed(sender: UIButton!) {
        // **NOT HERE**
        let alertView = UIAlertView();
        alertView.addButtonWithTitle("Ok");
        alertView.title = "non handler pressed";
        alertView.message = "non handler message";
        alertView.show();
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // let hc = HandlerClass()

        let button1 = UIButton()
        button1.frame = CGRectMake(100, 100, 100, 100)
        button1.backgroundColor =      UIColor.lightGrayColor().colorWithAlphaComponent(0.9)
        button1.layer.cornerRadius = 10.0
        button1.setTitle("Button ", forState: UIControlState.Normal)

        view.addSubview(button1)

        handler = HandlerClass()

        button1.addTarget(handler, action: "pressed:", forControlEvents: .TouchUpInside)
    }

}

Upvotes: 0

sunshinejr
sunshinejr

Reputation: 4854

From the Apple docs:

The target object—that is, the object to which the action message is sent. If this is nil, the responder chain is searched for an object willing to respond to the action message.

But you specified the target object, so whats up? Another interesting quote:

When you call this method, target is not retained.

And you created this object in a function, stored only there, so once the block ended, the retain count ended at 0, and the variable is now nil.

To avoid this, you could just add this as a property, before function, then instantiate in the function.

Upvotes: 1

sevensevens
sevensevens

Reputation: 1750

Figured it out thanks to Carl's comment.

The original problem was actually a little more complicated, I was creating a PopupMenuViewController in a long tap recognizer.

   func mapView(mapView: GMSMapView!, didLongPressAtCoordinate coordinate: CLLocationCoordinate2D)
   {
     let tapPoint = coordinate

     let menu = PopupMenuViewController();

     self.presentVC(menu)

     // menu is deallocated before exit (and before it can respond to pressed)
   }

The pressed function in PopupMenuViewController was destroyed before it could respond.

Upvotes: 0

Related Questions