Reputation: 1750
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
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
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
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