user2569905
user2569905

Reputation: 83

Action not being called when button is tapped in a stack view

I have a custom view that includes a stack view. Inside the stack view I have a label and a button.

I created my stack view, label and button in the following way and added them to the parent view.

class HomeView: UIView {

override init(frame: CGRect) {
    super.init(frame: frame)
    translatesAutoresizingMaskIntoConstraints = false

    addSubview(stackView)
    stackView.addArrangedSubview(haveAccount)
    stackView.addArrangedSubview(signin)
    stackView.setCustomSpacing(4.0, after: haveAccount)
}

let stackView: UIStackView = {
    let stack = UIStackView()
    stack.translatesAutoresizingMaskIntoConstraints = false
    stack.distribution = .fillProportionally
    stack.alignment = .fill
    stack.isUserInteractionEnabled = false
    return stack
}()

let haveAccount: UILabel = {
    let label = UILabel()
    ...
    return label
}()

let signin: UIButton = {
    let button = UIButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitle("Sign in", for: .normal)
    button.titleLabel?.font = UIFont(name: "Avenir", size: 14)
    button.setTitleColor(UIColor.white, for: .normal)
    button.addTarget(self, action: #selector(HomeController.loginClicked(_:)), for: .touchUpInside)
    return button
}()

}

In my view controller I add the view to the controller's base view and set the constraints. I also create the method that should be called when the signin button is tapped.

override func viewDidLoad() {
    super.viewDidLoad()

    homeView = HomeView()
    homeView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(homeView)
    homeView.fullscreenView(parentView: view)
}

@objc func loginClicked(_ sender: UIButton) {        
    print("sign in button pressed")
}

When I press the button the loginClicked method is not called. Now I did tried moving the loginClicked method to the custom view and changing the addTarget accordingly and loginClicked method is called. This being said I know the button is clickable but I don't think the target for the button action is correct and that is why the loginClicked method in the view controller is not being called.

Upvotes: 1

Views: 6158

Answers (4)

GuimarãesGabrielG
GuimarãesGabrielG

Reputation: 101

You need to add the right constraints, I had this problem, I had this problem and the solution was this.

Solution:

import Foundation
import UIKit

protocol HomeViewDelegate:class{
    func loginButtonClicked(sender: UIButton)
}

class HomeView: UIView {

    //2. Create a delegate
    weak var delegate: HomeViewDelegate?

    var stackView: UIStackView = {
        let stack = UIStackView()
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.distribution = .fillProportionally
        stack.alignment = .fill
        stack.axis = .vertical
        stack.isUserInteractionEnabled = true
        return stack
    }()

    let haveAccount: UILabel = {
        let label = UILabel()
        label.backgroundColor = .gray
        label.text = "Label"
        label.textAlignment = .center
        return label
    }()

    let signin: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Sign in", for: .normal)
        button.titleLabel?.font = UIFont(name: "Avenir", size: 14)
        button.setTitleColor(UIColor.white, for: .normal)
        button.addTarget(self, action: #selector(loginClicked(sender:)), for: .touchUpInside)
        button.isUserInteractionEnabled = true
        button.backgroundColor = .red
        return button
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        
        stackView.addArrangedSubview(haveAccount)
        stackView.addArrangedSubview(signin)
        stackView.setCustomSpacing(4.0, after: haveAccount)

        addSubview(stackView)
       
        NSLayoutConstraint.activate([
                
            self.stackView.topAnchor.constraint(equalTo: self.topAnchor),
            self.stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            self.stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            self.stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor)

        ])

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

   //3. Call your protocol method via delegate
   @objc func loginClicked(sender: UIButton) {
        if let delegate = delegate{
            delegate.loginButtonClicked(sender: sender)
        }
    }

}

ViewController

override func viewDidLoad() {
    super.viewDidLoad()

    let homeView = HomeView()

    homeView.translatesAutoresizingMaskIntoConstraints = false
    homeView.delegate = self

    self.view.addSubview(homeView)
        
     NSLayoutConstraint.activate([
            homeView.centerXAnchor.constraint(equalTo: centerXAnchor),
            homeView.centerYAnchor.constraint(equalTo: centerYAnchor),
            homeView.heightAnchor.constraint(equalToConstant: 300),
            homeView.widthAnchor.constraint(equalToConstant: 300)

     ])
}

@objc func loginClicked(_ sender: UIButton) {        
    print("sign in button pressed")
}

Upvotes: 1

willow
willow

Reputation: 301

add this line to your button code

button.isUserInteractionEnabled = true

re-active isUserInteractionEnabled again

let signin: UIButton = {
        let button = UIButton()
        button.backgroundColor = .blue
        button.setTitle("Sign in", for: .normal)
        button.titleLabel?.font = UIFont(name: "Avenir", size: 14)
        button.setTitleColor(UIColor.white, for: .normal)
        button.addTarget(self, action: #selector(ViewController.loginClicked(_:)), for: .touchUpInside)
        button.isUserInteractionEnabled = true

        return button
    }()

Upvotes: 0

Abhisheks
Abhisheks

Reputation: 104

You can use Protocol/Delegation

//1. Create a protocol

protocol HomeViewDelegate{
    func loginButtonClicked(sender: UIButton)
}

class HomeView: UIView {

    //2. Create a delegate
    var delegate: HomeViewDelegate?

    let stackView: UIStackView = {
        let stack = UIStackView()
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.distribution = .fillProportionally
        stack.alignment = .fill
        stack.isUserInteractionEnabled = false
        return stack
    }()

    let haveAccount: UILabel = {
        let label = UILabel()

        return label
    }()

    let signin: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Sign in", for: .normal)
        button.titleLabel?.font = UIFont(name: "Avenir", size: 14)
        button.setTitleColor(UIColor.white, for: .normal)
        button.addTarget(self, action: #selector(loginClicked(sender:)), for: .touchUpInside)
        button.backgroundColor = .red
        return button
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        translatesAutoresizingMaskIntoConstraints = false

        addSubview(stackView)
        stackView.addArrangedSubview(haveAccount)
        stackView.addArrangedSubview(signin)
        stackView.setCustomSpacing(4.0, after: haveAccount)

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

   //3. Call your protocol method via delegate
   @objc func loginClicked(sender: UIButton) {
        if let delegate = delegate{
            delegate.loginButtonClicked(sender: sender)
        }
    }

}

In You Caller ViewController create an extension

extension ViewController: HomeViewDelegate{
    func loginButtonClicked(sender: UIButton) {
        print("login Button Clicked")
    }
}

Upvotes: 3

emrepun
emrepun

Reputation: 2666

First of all you set userInteractionEnabled property of your stackView to false, set it to true. Then if it does not work consider the following approach:

There are two possible ways to fix this, first is adding the target from ViewController, and the other one is using delegation.

I think the first way would be easier to implement for you.

You need to add your target from your ViewController class.

First update your view class and get rid of adding a target:

class HomeView: UIView {

override init(frame: CGRect) {
  super.init(frame: frame)
  translatesAutoresizingMaskIntoConstraints = false

  addSubview(stackView)
  stackView.addArrangedSubview(haveAccount)
  stackView.addArrangedSubview(signin)
  stackView.setCustomSpacing(4.0, after: haveAccount)
}

let stackView: UIStackView = {
  let stack = UIStackView()
  stack.translatesAutoresizingMaskIntoConstraints = false
  stack.distribution = .fillProportionally
  stack.alignment = .fill
  stack.isUserInteractionEnabled = true
  return stack
}()

let haveAccount: UILabel = {
  let label = UILabel()
  ...
  return label
}()

let signin: UIButton = {
  let button = UIButton()
  button.translatesAutoresizingMaskIntoConstraints = false
  button.setTitle("Sign in", for: .normal)
  button.titleLabel?.font = UIFont(name: "Avenir", size: 14)
  button.setTitleColor(UIColor.white, for: .normal)
  return button
}()

}

Now in your ViewController:

override func viewDidLoad() {
    super.viewDidLoad()

    homeView = HomeView()
    homeView.translatesAutoresizingMaskIntoConstraints = false
    homeView.signIn.addTarget(self, action: #selector(loginClicked), for: .touchUpInside)
    view.addSubview(homeView)
    homeView.fullscreenView(parentView: view)
}

@objc func loginClicked(_ sender: UIButton) {        
    print("sign in button pressed")
}

Upvotes: 2

Related Questions