I basically want to have an invisible UIView that catches taps on it (and performs some action) but also passes through this tap event to the views behind it. I want to make it some generic class that inherits from UIView.
-
1https://stackoverflow.com/a/12355957/1801544 You can make a `PassthroughView` with just that and do what you need. – Larme Mar 04 '21 at 16:56
-
@Larme please pay attention that this 'PassthroughView' isn't what I asked. it's a view that checks all its subviews and if the UIEvent is in one of them it says that the event is in it too. I want the opposite. – Tamir Nahum Mar 04 '21 at 17:54
-
What I meant is that you can use that logic, created view and override `pointInside:withEvent:` apply your own logic inside that method to do what you need. – Larme Mar 04 '21 at 17:58
1 Answers
You can propagate touch events through the responder chain:
class PassTouchView: UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// do what you want here
print("View was touched!")
// propagate up the responder chain (most likely the superview)
self.next?.touchesBegan(touches, with: event)
}
}
Trying to do this with Gesture Recognizers is a bit different. If you want to do that, you probably want to use protocol/delegate pattern.
Edit
Quick example of protocol/delegate pattern when the "covering view" is using a UITapGestureRecognizer
.
The protocol:
protocol PassTapDelegate {
func passMeTheTap(_ recognizer: UITapGestureRecognizer)
}
A custom "pass the tap" view:
class PassTapView: UIView {
var ptDelegate: PassTapDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
let t = UITapGestureRecognizer(target: self, action: #selector(self.gotTap(_:)))
addGestureRecognizer(t)
}
@objc func gotTap(_ recognizer: UITapGestureRecognizer) -> Void {
print("Got Tap in PassTap view")
ptDelegate?.passMeTheTap(recognizer)
}
}
A sample view controller showing how to use it:
class PassTappingViewController: UIViewController, PassTapDelegate {
var btnsArray: [UIButton] = []
override func viewDidLoad() {
super.viewDidLoad()
let colors: [UIColor] = [
.systemBlue, .systemGreen, .systemOrange, .systemTeal,
]
let centers: [CGPoint] = [
CGPoint(x: -75.0, y: -75.0),
CGPoint(x: 75.0, y: -75.0),
CGPoint(x: -75.0, y: 75.0),
CGPoint(x: 75.0, y: 75.0),
]
var x: CGFloat = 0
var y: CGFloat = 0
var i: Int = 1
for (pt, c) in zip(centers, colors) {
let b = UIButton()
b.backgroundColor = c
b.setTitle("Button \(i)", for: [])
b.translatesAutoresizingMaskIntoConstraints = false
btnsArray.append(b)
view.addSubview(b)
// all buttons are 120 x 120
b.widthAnchor.constraint(equalToConstant: 120.0).isActive = true
b.heightAnchor.constraint(equalTo: b.widthAnchor).isActive = true
x = pt.x
y = pt.y
// uncomment these two lines to see the tap passing through
// to multiple overlapping buttons
//x = -75.0
//y = -75.0 + CGFloat(i) * 40.0
b.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: x).isActive = true
b.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: y).isActive = true
i += 1
}
// add a red PassTapView
let passTapView: PassTapView = PassTapView()
passTapView.translatesAutoresizingMaskIntoConstraints = false
passTapView.backgroundColor = .systemRed
view.addSubview(passTapView)
// center it so it's covering portions of the buttons
NSLayoutConstraint.activate([
passTapView.widthAnchor.constraint(equalToConstant: 120.0),
passTapView.heightAnchor.constraint(equalTo: passTapView.widthAnchor),
passTapView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
passTapView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
// add action for each button
btnsArray.forEach { b in
b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
}
// set self as the custom delegate for passTapView
passTapView.ptDelegate = self
}
@objc func btnTapped(_ sender: Any?) -> Void {
// unwrap the sender as a button and get its title
guard let b = sender as? UIButton,
let t = b.currentTitle
else {
return
}
print("Button \(t) was tapped!")
}
func passMeTheTap(_ recognizer: UITapGestureRecognizer) {
// loop through buttons array to see if tap from
// passTapView is inside any of them
btnsArray.forEach { b in
if b.bounds.contains(recognizer.location(in: b)) {
self.btnTapped(b)
}
}
}
}
The code will create 4 buttons in a grid, with a PassTapView
overlaid on top so it covers part of each button:
Tapping the Red view where it covers part of a button will "pass the tap" gesture to the delegate, which will trigger the .touchUpInside
action if the tap location is contained in a button.
If we un-comment two lines in the controller (see the comments), it will lay out the buttons in a stack so they overlap each other:
Now, tapping the left side of the red view will trigger the .touchUpInside
action for multiple buttons because the tap gesture location will fall inside more than one of them.

- 69,424
- 5
- 50
- 86
-
-
I want the view with Gesture Recognizers to be ahead of the second view which has a button on it. – Tamir Nahum Mar 07 '21 at 08:18
-
@TamirNahum - it's difficult to offer specific help for an abstract question. But, see the **Edit** in my answer... maybe that will get you on track. – DonMag Mar 07 '21 at 16:54