I'm calling a method from my DrawMenu
class in my ViewController
class, which draws an oval (currently circle) button, pretty simple. It draws the button perfectly, but if I tap the button it crashes.
This happens even though I have created an instance of the ViewController
class in DrawMenu
, and have used it for the 'target' parameter in 'button.addTarget'
Here is the code:
Button Method defined in DrawMenu
class:
func drawButton (superImageView: UIImageView, x_of_origin: CGFloat, y_of_origin: CGFloat, width_of_oval: CGFloat, height_of_oval: CGFloat, actionSelector: Selector, want_to_test_bounds:Bool) {
var VC = ViewController()
var button = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
button.addTarget(VC, action: actionSelector, forControlEvents: UIControlEvents.TouchUpInside)
button.frame = CGRect(x: x_of_origin, y: y_of_origin, width: width_of_oval, height: height_of_oval)
button.clipsToBounds = true
button.layer.cornerRadius = height_of_oval/2.0
if (want_to_test_bounds == true) {
button.layer.borderColor = UIColor.blackColor().CGColor
button.layer.borderWidth = 1.0
superImageView.userInteractionEnabled = true
superImageView.addSubview(button)
} else {
superImageView.userInteractionEnabled = true
superImageView.addSubview(button)
}
}
Method called in ViewController
class:
override func viewDidLoad() {
super.viewDidLoad()
var drawMenu = DrawMenu()
drawMenu.drawButton(imageView, x_of_origin: 100, y_of_origin: 150, width_of_oval: 100, height_of_oval: 100, actionSelector: "buttonTap:" as Selector, want_to_test_bounds: true)
}
buttonTap
also in ViewController
class:
func buttonTap(sender:UIButton!){
println("Button is working")
}
Any help is appreciated. Thank You
In the method drawButton
you are setting the target for the touchUpInside
to a new instance of the view controller. This reference is created in a local variable in drawButton
and will be released when that method exits. When the action handler is triggered it attempts to call the function on the invalid object and you get a crash.
The correct design pattern to use here is a delegate - Define a protocol for the handler and have your view controller implement that protocol. You can then pass the view controller to the drawButton
method -
Start by defining the protocol in DrawMenu -
protocol ButtonDelegate:NSObjectProtocol
{
func buttonTap(sender: UIButton!) -> Void
}
Then you can use the protocol reference in your drawButton
method -
func drawButton (superImageView: UIImageView, x_of_origin: CGFloat, y_of_origin: CGFloat, width_of_oval: CGFloat, height_of_oval: CGFloat, delegate: ButtonDelegate, want_to_test_bounds:Bool) {
var button = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
button.addTarget(delegate, action: "buttonTap:", forControlEvents: UIControlEvents.TouchUpInside)
button.frame = CGRect(x: x_of_origin, y: y_of_origin, width: width_of_oval, height: height_of_oval)
button.clipsToBounds = true
button.layer.cornerRadius = height_of_oval/2.0
if (want_to_test_bounds == true) {
button.layer.borderColor = UIColor.blackColor().CGColor
button.layer.borderWidth = 1.0
superImageView.userInteractionEnabled = true
superImageView.addSubview(button)
} else {
superImageView.userInteractionEnabled = true
superImageView.addSubview(button)
}
}
Finally, make sure your ViewController
implements the protocol -
class ViewController : UIViewController, ButtonDelegate
And pass the reference to the ViewController
instance when you create the button -
override func viewDidLoad() {
super.viewDidLoad()
var drawMenu = DrawMenu()
drawMenu.drawButton(imageView, x_of_origin: 100, y_of_origin: 150, width_of_oval: 100, height_of_oval: 100, delegate: self, want_to_test_bounds: true)
}
Other improvements I would suggest is making the drawButton
method a static, class method so you don't need to instantiate an instance of DrawMenu
to use it and having the method simply return the button rather than passing the method a view to add the button to. The way you have it, it is difficult to get a reference to the button if you want to make further changes. Changing the function in this way also makes it simple to add the button to views that aren't UIImageViews
Finally, use a CGRect rather than passing distinct x,y,width,height
class func drawButton (frame: CGRect, delegate: ButtonDelegate, showOutline:Bool) -> UIButton {
var button = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
button.addTarget(delegate, action: "buttonTap:", forControlEvents: UIControlEvents.TouchUpInside)
button.frame = frame
button.clipsToBounds = true
button.layer.cornerRadius = frame.size.height/2.0
if (showOutline) {
button.layer.borderColor = UIColor.blackColor().CGColor
button.layer.borderWidth = 1.0
}
return button
}
Then you would say -
override func viewDidLoad() {
super.viewDidLoad()
var newButton = DrawMenu.drawButton(CGRect(x: 100, y: 150, width: 100, height: 100),
delegate: self,
showOutline: true)
imageView.userInteractionEnabled = true
imageView.addSubview(newButton)
}