I’ve two overlapping UIViews.
The again view has a UITapGestureRecognizer, and the entrance view has one other gesture recognizer, for instance a UILongPressGestureRecognizer or a UIPanGestureRecognizer.
What I need is:
- if the person performs the gesture dealt with by the entrance view, the entrance view ought to deal with it;
- if the person performs one other gesture, equivalent to a faucet, the view behind ought to obtain it;
- the entrance view mustn’t block gestures it doesn’t deal with.
I initially had the difficulty with a pan gesture, however the identical drawback additionally occurs with an extended press gesture.
I attempted setting cancelsTouchesInView = false, and I additionally tried utilizing UIGestureRecognizerDelegate, however the entrance view nonetheless wins hit-testing. Because of this, the faucet gesture on the view behind is just not triggered when tapping contained in the overlapping space.
Here’s a minimal instance utilizing an extended press gesture on the entrance view:
import SwiftUI
import UIKit
struct TestGestureConflict: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> TestGestureConflictViewController {
TestGestureConflictViewController()
}
func updateUIViewController(_ uiViewController: TestGestureConflictViewController, context: Context) {}
}
ultimate class TestGestureConflictViewController: UIViewController {
personal let tapView = UIView()
personal let longPressView = UIView()
override func viewDidLoad() {
tremendous.viewDidLoad()
view.backgroundColor = .systemBackground
configureTapView()
configureLongPressView()
configureGestures()
}
personal func configureTapView() {
tapView.translatesAutoresizingMaskIntoConstraints = false
tapView.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.35)
tapView.layer.borderColor = UIColor.systemBlue.cgColor
tapView.layer.borderWidth = 2
view.addSubview(tapView)
NSLayoutConstraint.activate([
tapView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
tapView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
tapView.widthAnchor.constraint(equalToConstant: 120),
tapView.heightAnchor.constraint(equalToConstant: 120)
])
}
personal func configureLongPressView() {
longPressView.translatesAutoresizingMaskIntoConstraints = false
longPressView.backgroundColor = UIColor.systemOrange.withAlphaComponent(0.35)
longPressView.layer.borderColor = UIColor.systemOrange.cgColor
longPressView.layer.borderWidth = 2
view.addSubview(longPressView)
NSLayoutConstraint.activate([
longPressView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
longPressView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
longPressView.widthAnchor.constraint(equalToConstant: 220),
longPressView.heightAnchor.constraint(equalToConstant: 220)
])
}
personal func configureGestures() {
let tapGesture = UITapGestureRecognizer(goal: self, motion: #selector(handleTap))
tapGesture.delegate = self
tapView.addGestureRecognizer(tapGesture)
let longPressGesture = UILongPressGestureRecognizer(goal: self, motion: #selector(handleLongPress))
longPressGesture.cancelsTouchesInView = false
longPressGesture.delegate = self
longPressView.addGestureRecognizer(longPressGesture)
}
@objc personal func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
guard gestureRecognizer.state == .ended else { return }
print("faucet")
}
@objc personal func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
change gestureRecognizer.state {
case .started:
print("lengthy press started")
case .ended, .cancelled:
print("lengthy press ended")
default:
break
}
}
}
extension TestGestureConflictViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive contact: UITouch) -> Bool {
// That is referred to as for the lengthy press gesture, however returning true or false
// doesn't make the tapView behind obtain the faucet within the overlapping space.
return true
}
}
On this instance, longPressView is added above tapView.
Anticipated conduct:
- lengthy press contained in the orange view → dealt with by longPressView;
- faucet contained in the overlapping blue/orange space → dealt with by tapView.
Precise conduct:
- lengthy press works on the entrance view;
- faucet doesn’t attain the view behind;
- cancelsTouchesInView = false doesn’t remedy the issue as a result of the entrance view remains to be the results of hit-testing.
My understanding is that this isn’t actually about UIPanGestureRecognizer or UILongPressGestureRecognizer, however about UIKit hit-testing: as soon as the entrance view is chosen because the hit-tested view, the view behind doesn’t take part in contact supply.
What’s the appropriate UIKit strategy for this?
Ought to I remedy this with:
- customized hitTest(_:with:) / level(inside:with:),
- a UIGestureRecognizerDelegate,
- manually forwarding touches,
- attaching the gesture recognizer to a typical dad or mum view,
- or altering the view hierarchy?
What’s the really helpful technique to let a entrance view deal with solely a particular gesture whereas permitting different gestures, like faucets, to be dealt with by views behind it?
