Shivam Pokhriyal
Shivam Pokhriyal

Reputation: 1104

iOS CustomView With AutoLayout in navigationItem not receiving clicks

I created a custom view for navigationItem but somehow it is not receiving any click events:

The code for customView is below

class CustomNavigationView: UIView {

let backButton: UIButton = {
    let button = UIButton(type: .custom)
    button.setImage(UIImage(named: "icon_back", in: Bundle.main, compatibleWith: nil), for: .normal)
    button.isUserInteractionEnabled = true
    return button
}()

var profileImage: UIImageView = {
    let imageView = UIImageView()
    imageView.image = UIImage(named: "icon_back", in: Bundle.main, compatibleWith: nil)
    return imageView
}()

var profileName: UILabel = {
    let label = UILabel()
    label.text = "No Name"
    label.font = UIFont(name: "HelveticaNeue", size: 16) ?? UIFont.systemFont(ofSize: 16)
    label.textColor = UIColor(red: 96, green: 94, blue: 94)
    return label
}()

var onlineStatusIcon: UIView = {
    let view = UIView()
    view.backgroundColor = UIColor(28, green: 222, blue: 20)
    return view
}()

var onlineStatusText: UILabel = {
    let label = UILabel()
    label.text = "Online"
    label.font = UIFont(name: "HelveticaNeue", size: 12) ?? UIFont.systemFont(ofSize: 12)
    label.textColor = UIColor(red: 113, green: 110, blue: 110)
    return label
}()

lazy var profileView: UIStackView = {
    let stackView = UIStackView(arrangedSubviews: [self.profileName, self.onlineStatusText])
    stackView.alignment = .fill
    stackView.axis = .vertical
    stackView.distribution = .fillEqually
    stackView.spacing = 2
    return stackView
}()

@objc func backButtonClicked(_ sender: UIButton) {
    print("Back Button click successfully")
}

private func setupConstraints() {
    self.addViewsForAutolayout(views: [backButton, profileImage, onlineStatusIcon, profileView])
    //Setup constraints
    backButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5).isActive = true
    backButton.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
    backButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10).isActive = true
    backButton.widthAnchor.constraint(equalToConstant: 20).isActive = true

    profileImage.leadingAnchor.constraint(equalTo: backButton.trailingAnchor, constant: 20).isActive = true
    profileImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
    profileImage.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -5).isActive = true
    profileImage.widthAnchor.constraint(equalToConstant: 35).isActive = true
    profileImage.layer.cornerRadius = 18
    profileImage.clipsToBounds = true

    onlineStatusIcon.bottomAnchor.constraint(equalTo: profileImage.bottomAnchor, constant: 0).isActive = true
    onlineStatusIcon.leadingAnchor.constraint(equalTo: profileImage.trailingAnchor, constant: -8).isActive = true
    onlineStatusIcon.widthAnchor.constraint(equalToConstant: 10).isActive = true
    onlineStatusIcon.heightAnchor.constraint(equalToConstant: 10).isActive = true
    onlineStatusIcon.layer.cornerRadius = 5
    onlineStatusIcon.clipsToBounds = true

    profileView.leadingAnchor.constraint(equalTo: profileImage.trailingAnchor, constant: 5).isActive = true
    profileView.topAnchor.constraint(equalTo: profileImage.topAnchor).isActive = true
    profileView.bottomAnchor.constraint(equalTo: profileImage.bottomAnchor).isActive = true

}

required init() {
    super.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 40))
    setupConstraints()
    addButtonTarget()
}

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

func addButtonTarget() {
    // Setup button callback
    backButton.addTarget(self, action: #selector(backButtonClicked(_:)), for: .touchUpInside)

    print("Target added")
}
}

And I am setting this view as NavigationbarLeft button Item in my view Controller:

class ViewController:  UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    let customView = CustomNavigationView()
    self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: customView)
}
}

The view is displaying correctly but the clicks are not working at all. I used view debugging to check if some other layer is on top of this which might be causing problem but nothing of that sort is present. I also checked backButton frame when adding the target using debug points.

Is there any solution for this problem. Does autolayout not work with custom view in navigation item? Or is there something that I am missing.

You can run the above piece of code and see that the clicks are not working.

This somehow appears to be related to auto layout. If I hardcode the frame position then clicks are working.

class CustomNavigationView: UIView {

let backButton: UIButton = {
    let button = UIButton(frame: CGRect(x: 5, y: 5, width: 30, height: 30))
    button.setImage(UIImage(named: "icon_back", in: Bundle.kommunicate, compatibleWith: nil), for: .normal)
    button.isUserInteractionEnabled = true
    return button
}()

var profileImage: UIImageView = {
    let imageView = UIImageView(frame: CGRect(x: 40, y: 5, width: 30, height: 30))
    imageView.image = UIImage(named: "icon_back", in: Bundle.kommunicate, compatibleWith: nil)
    return imageView
}()

var profileName: UILabel = {
    let label = UILabel(frame: CGRect(x: 80, y: 5, width: 50, height: 15))
    label.text = "No Name"
    label.font = UIFont(name: "HelveticaNeue", size: 16) ?? UIFont.systemFont(ofSize: 16)
    label.textColor = UIColor(red: 96, green: 94, blue: 94)
    return label
}()

var onlineStatusIcon: UIView = {
    let view = UIView(frame: CGRect(x: 65, y: 30, width: 10, height: 10))
    view.backgroundColor = UIColor(28, green: 222, blue: 20)
    return view
}()

var onlineStatusText: UILabel = {
    let label = UILabel(frame: CGRect(x: 80, y: 25, width: 50, height: 10))
    label.text = "Online"
    label.font = UIFont(name: "HelveticaNeue", size: 12) ?? UIFont.systemFont(ofSize: 12)
    label.textColor = UIColor(red: 113, green: 110, blue: 110)
    return label
}()

lazy var profileView: UIStackView = {
    let stackView = UIStackView(arrangedSubviews: [self.profileName, self.onlineStatusText])
    stackView.alignment = .fill
    stackView.axis = .vertical
    stackView.distribution = .fillEqually
    stackView.spacing = 2
    return stackView
}()

@objc func backButtonClicked(_ sender: UIButton) {
    print("Back button is successfully called")
}

private func setupConstraints() {
    self.addSubview(backButton)
    self.addSubview(profileImage)
    self.addSubview(onlineStatusIcon)
    self.addSubview(profileView)
}

required init() {
    super.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 40))
    setupConstraints()
    addButtonTarget()
}

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

func addButtonTarget() {
    // Setup button callback
    backButton.addTarget(self, action: #selector(backButtonClicked(_:)), for: .touchUpInside)

    print("Target added")
}
}

Upvotes: 0

Views: 314

Answers (1)

Aris
Aris

Reputation: 1559

The problem is with the manually added constraints that you added. Using the view debugger the width of CustomNavigationView after it is added to the bar is 0.

In order to force the container to expand, add the following constraint in setupConstraints():

profileView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true

Now that the container expands to match it's contents, the touch events should be propagated to the button as expected.

Upvotes: 2

Related Questions