UIButton addTarget Selector is not working - ios

SquareBox.swift
class SquareBox {
func createBoxes() {
for _ in 0..<xy {
let button = UIButton()
button.backgroundColor = .white
button.setTitleColor(UIColor.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.black.cgColor
stack.addArrangedSubview(button)
button.addTarget(self, action: #selector(click(sender:)) , for: .touchUpInside)
}
}
#objc func click(sender : UIButton) {
print("Click")
}
}
ViewController.swift
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let boxRow = SquareBox()
boxRow.createBoxes()
}
}
Also I've tried #IBAction instead of #objc, it doesn't work, but if I use "click" function in ViewController.swift that I created this object, it's working but I need this function inside of this class.

Now that you have posted relevant information in your question, the problem is quite clear. You have a memory management issue.
In your GameViewController's viewDidLoad you create a local instance of SquareBox. This local instance goes out of scope at the end of viewDidLoad. Since there is no other reference to this instance, it gets deallocated at the end of viewDidLoad.
Since the instance of SquareBox has been deallocated, it is not around to act as the button's target. And your click method is never called.
The solution is to keep a reference in your view controller:
class GameViewController: UIViewController {
let boxRow = SquareBox()
override func viewDidLoad() {
super.viewDidLoad()
boxRow.createBoxes()
}
}

var btnfirst:UIButton!
override func viewDidLoad()
{
super.viewDidLoad()
btnfirst = UIButton(type: .system)
btnfirst.setTitle("Press", for: .normal)
btnfirst.setTitleColor(.red, for: .normal)
btnfirst.frame = CGRect(x: 100, y: 200, width: 100, height: 30)
btnfirst.addTarget(self, action: #selector(benpress( sender:)),for: .touchUpInside)
self.view.addSubview(btnfirst)
}
func benpress( sender :UIButton)
{
//Your Code Here
}

For those who did not find a solution, here is mine.
If you constructed your UIButton as
let button: UIButton = {
return UIButton()
}()
Just convert those into
lazy var button: UIButton = {
return UIButton()
}()
I think this is because of somewhat deallocation as mentioned above.

button.addTarget(self, action:#selector(self.click), for: .touchUpInside)
func click(sender : UIButton) {
// code here
}

I guess the issue is how you are setting up layout of your buttons.
Try this:
func createBoxes() {
stack.backgroundColor = UIColor.red
for _ in 0..<xy {
// Create the button
let button = UIButton()
button.backgroundColor = UIColor.red
// Add constraints
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
// Setup the button action
button.addTarget(self, action: #selector(SquareBox.click(sender:)), for: .touchUpInside)
// Add the button to the stack
stack.addArrangedSubview(button)
}
}
#objc func click(sender : UIButton) {
print("Click")
}

button.addTarget(self, action: #selector(self.buttonTapped), for: .touchUpInside)
func buttonTapped(sender : UIButton) {
// code here
}

Replace with this :
btn.addTarget(self, action: #selector(self.click(sender:)), for: .touchUpInside)
I think something else effect to your selector method try to find in your code because your code also working in my project.

Related

Action of UIButton doesn't work if the button is initialized with let and the addTarget in it in custom view, but it works in UIViewController

When I create a button like below in the custom view the action does not work:
private let usernameButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.black, for: .normal)
button.setTitle("someTitle", for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 13)
button.addTarget(self, action: #selector(didTapUsername), for: .touchUpInside)
return button
}()
However, it works when I do the same in UIViewController somehow.
It always works if I make it lazy var but couldn't understand why it doesn't work if I make it let in custom view while It is working in UIViewController
Thanks for any comment.
self inside that block actually is the block, not the view(you can see it using print(self)), probably because it's not yet initialised(same reason why you can't use self before super.init), that's why I usually add targets/delegates outside of the initialisation block. With lazy var self is already initialised, so no problem should be there.
I'm not sure why it works at all, seems kind of magic for me, and I wouldn't depend on it.
Anyway, it works fine with both custom ViewController and custom View in my case: I've added CustomView as subview to ViewController in the storyboard.
class ViewController: UIViewController {
private let usernameButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.black, for: .normal)
button.setTitle("someTitle", for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 13)
button.addTarget(self, action: #selector(didTapUsername), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.insertSubview(usernameButton, at: 0)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
usernameButton.frame = view.bounds
}
#objc
func didTapUsername() {
print("ViewController", #function)
}
}
class CustomView: UIView {
private let usernameButton: UIButton = {
let button = UIButton(type: .system)
button.setTitleColor(.black, for: .normal)
button.setTitle("someTitle", for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 13)
button.addTarget(self, action: #selector(didTapUsername), for: .touchUpInside)
return button
}()
override func awakeFromNib() {
super.awakeFromNib()
addSubview(usernameButton)
}
override func layoutSubviews() {
super.layoutSubviews()
usernameButton.frame = bounds
}
#objc
func didTapUsername() {
print("CustomView", #function)
}
}
This shouldn't do differently for UIViewController/UIView, check out your logic

Adding a target to button inside a closure doesn't work

The following code is located inside a subclass of UIView
I am setting up a cancelButton inside a closure:
private var cancelButtonClosure: UIButton = {
...
button.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
}()
And at first I instantiated the button inside a function like so:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
...
addSubview(cancelButton)
...
}
However this resulted in the cancel function not being called at all (even though the layout was right and the button was highlighting)
So I made these change:
Removed the addTarget part from the cancelButtonClosure
Added the addTarget part inside the showConfirmationView function
So it looked like that:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
cancelButton.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
addSubview(cancelButton)
...
}
It worked: the cancel function was called; but I don't know why. I'm really curious to know why what I did before did not work. Thanks for your insights!
Check your implementation because a setup like this works as expected:
private var cancelButton: UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Cancel", for: .normal)
btn.addTarget(self, action: #selector(cancelSomething(_:)), for: .touchUpInside)
return btn
}()
#objc func cancelSomething(_ sender: UIButton) {
print("Something has to be cancelled")
}
override func viewDidLoad() {
super.viewDidLoad()
showConfirmationView()
}
func showConfirmationView() {
cancelButton.sizeToFit()
cancelButton.center = view.center
view.addSubview(cancelButton)
}

UIButton not performing action from function in a different class

I have a class where written is a function creating my button:
LoginButton.swift
func createButton() {
let myButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(Foo().buttonPressed(_:)), for: .touchUpInside)
}()
}
Now in my second class, Foo.swift, I have a function that just prints a statement
Foo.swift
#objc func buttonPressed(_ sender: UIButton) {
print("button was pressed")
}
When ran I get no errors except when I try to press the button, nothing happens. Nothing prints, the UIButton doesn't react in any way. Really not sure where the error occurs because Xcode isn't printing out any type of error or warning message.
The action method is called in the target object. Thus, you have either to move buttonPressed to the class which contains createButton or to pass an instance of Foo as a target object.
But note that a button is not the owner of its targets. So, if you just write:
button.addTarget(Foo(), action: #selector(buttonPressed(_:)), for: .touchUpInside)
This will not work, because the Foo object is immediately released after that line. You must have a strong reference (e.g. a property) to Foo() like
let foo = Foo()
func createButton() {
let myButton: UIButton = {
let button = UIButton()
button.addTarget(foo, action: #selector(buttonPressed(_:)), for: .touchUpInside)
}()
}
You are missing with target. So make instant of target globally and make use of it as target for button action handler.
class ViewController: UIViewController {
let foo = Foo()
override func viewDidLoad() {
super.viewDidLoad()
createButton()
}
func createButton() {
let myButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
button.backgroundColor = UIColor.red
button.setTitle("Tap me", for: .normal)
button.addTarget(self.foo, action: #selector(self.foo.buttonPressed(_:)), for: .touchUpInside)
return button
}()
myButton.center = self.view.center
self.view.addSubview(myButton)
}
}
Class Foo:
class Foo {
#objc func buttonPressed(_ sender: UIButton) {
print("button was pressed")
}
}
Just pass Selector as function argument.
func createButtonWith(selector: Selector) {
let myButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: selector), for: .touchUpInside)
}()
}
And call this function like below...
createButtonWith(selector: #selector(Foo().buttonPressed(_:)))

Working with several view controllers in Swift

I am developing a very simple Swift application programmatically (without storyboards). I have a HomeViewController
import UIKit
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func createButton(){
let button = UIButton()
button.setTitle("Start", forState: .Normal)
button.setTitleColor(UIColor.blueColor(), forState: .Normal)
button.frame = CGRectMake(100,200,50,50)
button.addTarget(self, action: "timerPressed:", forControlEvents: .TouchUpInside)
self.view.addSubview(button)
}
}
And I have the following
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let home = HomeViewController()
home.createButton()
}
}
When I call home.createButton() i expect a button to display, but currently it does not. I know the method works because when I moved that createButton() method into the ViewController class it displays the button. Am I missing something with how to properly call the createButton method from within the HomeViewController. Is there a better convention to do what I am trying to do?
class func createButton(controller: UIViewcontroller){
let button = UIButton()
button.setTitle("Start", forState: .Normal)
button.setTitleColor(UIColor.blueColor(), forState: .Normal)
button.frame = CGRectMake(100,200,50,50)
button.addTarget(self, action: "timerPressed:", forControlEvents: .TouchUpInside)
controller.view.addSubview(button)
}
Now call this method to any ViewController
Like This :
HomeViewController.createButton(self).
You can write createButton code under loadview() method of your HomeViewController
class HomeViewController: UIViewController {
override func loadView() {
super.loadView()
let button = UIButton()
button.setTitle("Start", forState: .Normal)
button.setTitleColor(UIColor.blueColor(), forState: .Normal)
button.frame = CGRectMake(100,200,50,50)
button.addTarget(self, action: "timerPressed:", forControlEvents: .TouchUpInside)
self.view.addSubview(button)
}
}
so there is no need to call extra method of HomeViewController to add button as a subview of it.

PushViewController from another UIViewController - Swift

I have a class like this,
import UIkit
class One {
let btn = UIButton()
override func viewDidLoad(){
super.viewDidLoad()
btn.frame = CGRectMake(10, 20, 30, 30)
btn.setTitle("Go", forState: UIControlState.Normal)
btn.addTarget(self, action: "goToClassTwo", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(btn)
}
func goToClassTwo(){
if(AppGlobals().getIsFromDiffView()){
let difView = UINavigationController(rootViewController: DiffView())
difView.pushViewController(Two(), animated: true)
}else{
self.navigationController?.pushViewController(Two(), animated: true)
}
}
}
A setter/getter class like this,
class AppGlobals: NSObject {
var isFromDiffView = false
func setIsFromDiffView(val: Bool){
isFromDiffView = val
}
func getIsFromDiffView() -> Bool {
return isFromDiffView
}
}
And I have another class like this,
class DiffView {
let btn = UIButton()
override func viewDidLoad(){
super.viewDidLoad()
btn.frame = CGRectMake(10, 20, 30, 30)
btn.setTitle("Push", forState: UIControlState.Normal)
btn.addTarget(self, action: "btnAction", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(btn)
}
func btnAction(){
AppGlobals().setIsFromDiffView(true)
One().goToClassTwo()
}
}
I am facing a problem here. When the 'Go' button in the class 'One' is tapped, then the 'Two' view controller is shown. But when I tap on the 'Push' button in the class 'DiffView' is tapped, the 'Two' view controller is not being shown.
I have checked setting breakpoints. The control does come to the goToClassTwo function in the class 'One' and the if path is being executed. But the 'Two' view controller is not shown. difView.pushViewController is called. But it is not pushing to the next view.
NOTE: I am not using storyboard
Any help would be appreciated!
This is the updated code.
Code for class 'One':
import UIKit
class One {
let btn = UIButton()
override func viewDidLoad(){
super.viewDidLoad()
btn.frame = CGRectMake(10, 20, 30, 30)
btn.setTitle("Go", forState: UIControlState.Normal)
btn.addTarget(self, action: "goToClassTwo", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(btn)
}
func goToClassTwo(){
if(AppGlobals().getIsFromDiffView()){
//Using the navigation controller of DiffView
AppGlobals().getController().pushViewController(Two(), animated: true)
}else{
self.navigationController?.pushViewController(Two(), animated: true)
}
}
}
setter/getter class:
class AppGlobals: NSObject {
var isFromDiffView = false
var cntrlr: UINavigationController!
func setIsFromDiffView(val: Bool){
isFromDiffView = val
}
func getIsFromDiffView() -> Bool {
return isFromDiffView
}
//Setting and getting DiffView Navigation controller
func setController(cntrl: UINavigationController){
cntrlr = cntrl
}
func getController() -> UINavigationController {
return cntrlr
}
}
DiffView class:
class DiffView {
let btn = UIButton()
override func viewDidLoad(){
super.viewDidLoad()
btn.frame = CGRectMake(10, 20, 30, 30)
btn.setTitle("Push", forState: UIControlState.Normal)
btn.addTarget(self, action: "btnAction", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(btn)
}
func btnAction(){
AppGlobals().setIsFromDiffView(true)
//Setting the navigation controller
AppGlobals().setController(self.navigationController!)
One().goToClassTwo()
}
}
With this updated code, class 'Two' view controller is being displayed.
Thank you #zp_x for your help.

Resources