Keater Keater - 4 months ago 37
Objective-C Question

UIButton not working on a subview of UIWindow

I'm trying to create a custom one-button AlertViewController in Swift, I use window.addSubview() to show the AlertView, but when touch the button on the AlertView, the func buttonTapped() is not working, below is my code, please tell me what's wrong here, thanks.

MyAlertViewController.swift

class MyAlertViewController: UIViewController {
var button: UIButton!
var contentView: UIView!

override func viewDidLoad() {
super.viewDidLoad()
setUp()
}

func setUp(){
contentView = UIView(frame: CGRectMake(0,0,200,300))
contentView.center = view.center
contentView.backgroundColor = UIColor.whiteColor()
contentView.layer.cornerRadius = 10

button = UIButton(type: .System)
button.frame = CGRectMake(50, 150, 100, 40)
button.setTitle("button", forState: UIControlState.Normal)
button.addTarget(self, action: #selector(buttonTapped(_:)), forControlEvents: UIControlEvents.TouchUpInside)

view.addSubview(contentView)
contentView.addSubview(button)
view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.1)
view.userInteractionEnabled = true
}

func buttonTapped(sender: AnyObject?){
print("button tapped")
}

func show(){
let window = UIApplication.sharedApplication().keyWindow! as UIWindow
view.frame = window.bounds
window.addSubview(view)
}
}


ParentViewController.swift (is the rootViewController of my window)

class ParentViewController: UIViewController {
var button: UIButton!

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.whiteColor()
button = UIButton(type: .System)
button.frame = CGRectMake(110,269,100,30)
button.setTitle("show", forState: .Normal)
button.addTarget(self, action: #selector(buttonTapped(_:)), forControlEvents: .TouchUpInside)
view.addSubview(button)

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}

func buttonTapped(sender: AnyObject?){
MyAlertViewController().show()
}

}


enter image description here

I found out if I change ParentViewController.swift as below, the button on the alert view can work correctly.

class ParentViewController: UIViewController {
var button: UIButton!
var vc: MyAlertViewController!

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.cyanColor()
button = UIButton(type: .System)
button.frame = CGRectMake(110,269,100,30)
button.setTitle("show", forState: .Normal)
button.addTarget(self, action: #selector(buttonTapped(_:)), forControlEvents: .TouchUpInside)
view.addSubview(button)

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}

func buttonTapped(sender: AnyObject?){
vc = MyAlertViewController()
vc.show()
}

}


But I intended to show this alert view each time the user get new messages,so it would show up at any time and on any ViewController , so I don't want to declare it in each ViewController, so How can I solve this?

Answer

TLDR,

You have to retain an instance of MyAlertViewController().

Change to this and it will work (as you've already done):

// keep some reference
var alert: MyAlertViewController?

func buttonTapped(sender: AnyObject?){
    alert = MyAlertViewController()
    alert?.show()
}

More explanation,

the button.addTarget(self, ...) that is called inside MyAlertViewController does not retain self.

The last line of the doc of addTarget function said that:

// ... the action cannot be NULL. Note that the target is not retained.*

So there will be no self to send action to after leaving of this function:

func buttonTapped(sender: AnyObject?){
    MyAlertViewController().show()
}

Another option, is to keep self variable in MyAlertViewController:

// retain self manually
var mySelf: MyAlertViewController?

func setUp(){
    ...
    // reference to self
    mySelf = self
    button.addTarget(mySelf, action: #selector(buttonTapped(_:)), forControlEvents: UIControlEvents.TouchUpInside)
}
Comments