manban
manban

Reputation: 133

Swift add GestureRecognizer to View on top of UIStackView

I'm trying to add a GestureRecognizer to a UIView which is an arranged subview of a UIStackView. I tried already this this or this. Also I tried to enable all underlying views for userInteraction, but it didn't work.

When I add the gesture recognizer to the imageView which is the parent view of the stackView, then the tap works. Strangely it also get's triggered when I tap on the "bannerView" which is a subview of the stackView and therefore lies on top of the imageView.

What's the proper way to get the tap gesture recognize on top of a stackViews's arrangedSubview?

Here my code:

import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {
var imageView: UIImageView!
var bannerStackView: UIStackView!
var bannerView: UIView!

override func loadView() {
    let view = UIView()
    view.backgroundColor = .white
    self.view = view

    setupImageView()
    setupBannerStackView()
    setupConstraints()
}

func setupImageView() {
    let image = UIImage(named: "Apple.jpeg")
    imageView = UIImageView(image: image)
    imageView.backgroundColor = .red
    view.addSubview(imageView)
}

func setupBannerStackView() {
    bannerStackView = UIStackView()
    bannerStackView.axis = .vertical
    bannerStackView.alignment = .leading
    bannerStackView.distribution = .equalCentering
    bannerStackView.isUserInteractionEnabled = true

    bannerView = UIView()
    bannerView.backgroundColor = .blue

    bannerStackView.addArrangedSubview(bannerView)
    imageView.addSubview(bannerStackView)

    /*
     Also tried this but didn't work
     */
//        let tapGesture = UITapGestureRecognizer(target: self, action: 
#selector(onBannerTapped))
//        bannerView.isUserInteractionEnabled = true
//        bannerView.isUserInteractionEnabled = true
//        bannerView.addGestureRecognizer(tapGesture)

    for bannerView in bannerStackView.arrangedSubviews {
        let tapGesture = UITapGestureRecognizer(target: self, action: 
#selector(onBannerTapped))
        bannerView.isUserInteractionEnabled = true
        bannerView.addGestureRecognizer(tapGesture)
    }

}

@objc func onBannerTapped() {
    print("Banner view tapped!")
}

func setupConstraints() {
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    imageView.widthAnchor.constraint(equalToConstant: 200).isActive = true
    imageView.heightAnchor.constraint(equalToConstant: 200).isActive = true

    bannerStackView.translatesAutoresizingMaskIntoConstraints = false
    bannerStackView.topAnchor.constraint(equalTo: imageView.topAnchor, constant: 10).isActive = true
    bannerStackView.leadingAnchor.constraint(equalTo: imageView.leadingAnchor).isActive = true

    bannerView.translatesAutoresizingMaskIntoConstraints = false
    bannerView.heightAnchor.constraint(equalToConstant: 30).isActive = true
    bannerView.widthAnchor.constraint(equalToConstant: 80).isActive = true
}
}

Upvotes: 0

Views: 2415

Answers (1)

DonMag
DonMag

Reputation: 77690

.isUserInteractionEnabled cascades to subviews. So, even if I set it to true on a subview, if the subview's superview (or its superview, and on up the hierarchy) has .isUserInteractionEnabled set to false, the subview will not get touch events.

So, the first thing to do is check .isUserInteractionEnabled on each view in the hierarchy.

In your func, remove this line:

    bannerStackView.isUserInteractionEnabled = true

then add these lines after creating your tapGesture:

    print("imageView isUserInteractionEnabled:", imageView.isUserInteractionEnabled)
    print("bannerStackView isUserInteractionEnabled:", bannerStackView.isUserInteractionEnabled)
    print("bannerView isUserInteractionEnabled:", bannerView.isUserInteractionEnabled)

So it looks like this:

func setupBannerStackView() {
    bannerStackView = UIStackView()
    bannerStackView.axis = .vertical
    bannerStackView.alignment = .leading
    bannerStackView.distribution = .equalCentering

    // remove this line (comment-it out)
    // bannerStackView.isUserInteractionEnabled = true

    bannerView = UIView()
    bannerView.backgroundColor = .blue

    bannerStackView.addArrangedSubview(bannerView)
    imageView.addSubview(bannerStackView)

    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onBannerTapped))
    bannerView.addGestureRecognizer(tapGesture)

    print("view isUserInteractionEnabled:", view.isUserInteractionEnabled)
    print("imageView isUserInteractionEnabled:", imageView.isUserInteractionEnabled)
    print("bannerStackView isUserInteractionEnabled:", bannerStackView.isUserInteractionEnabled)
    print("bannerView isUserInteractionEnabled:", bannerView.isUserInteractionEnabled)

}

You should see this output:

view isUserInteractionEnabled: true
imageView isUserInteractionEnabled: false
bannerStackView isUserInteractionEnabled: true
bannerView isUserInteractionEnabled: true

and that tells you.... you simply need to add:

imageView.isUserInteractionEnabled = true

Upvotes: 3

Related Questions