Avoid Passing Touch Event to the Background View Behind - ios

View
StackView (vertical)
Purple (View)
Green (View)
Orange (View)
View has a tap gesture recognizer. It prints "this" plus a random int.
If I tap Purple, Green or Orange, it still prints "this".
How can I disable taps on the colored boxes? I tried turning off the 'User Interaction Enabled' setting on the boxes.

Because the stack view is a sub-view of View and not added over the top (like a presented view controller), its going to receive the touch events like you're experiencing. Your view controller needs to adopt the UIGestureRecognizerDelegate protocol, then you need to set the gesture's delegate to the view controller (most likely you just need to set it to "self") and then implement the following function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
//the below will return false if the touch's view is not the gesture recognizer's view
return touch.view == gestureRecognizer.view
}
The code example was referenced from: UITapGestureRecognizer tap on self.view but ignore subviews

Related

UISearchBar Sometimes Resigns When Tapping On Views

My view controller setup is as follows: UISearchBar on top of screen, keyboard on the bottom, and other views between them: custom UIView, UIView, UITableView. When I tap on the table view cells and UIButtons inside UIViews, I can trigger my actions successfully while keeping UISearchBar still visible on screen. But when I tap on UIViews that do not have buttons, this triggers searchBarShouldEndEditing as if search bar loses the focus. I want to disable this behavior and let only Cancel button and keyboard's Done button to trigger UISearchBar closure.
I thought this has to do with bubbling effect and tried to implement this fix:
extension UIViewController : UIGestureRecognizerDelegate{
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
#IBAction func didTap(_ gestureRecognizer : UITapGestureRecognizer ) {
//
}
but this did not address the issue and UISearchBar still calls searchBarShouldEndEditing. Of course, I could add logic to searchBarShouldEndEditing to return false but not sure this is effective enough.
How can I ensure UISearchBar remains in place while tapping on other elements like UIViews?

UITableViewCell not intercepting scroll events

I have a tableView in which some cells have a UICollectionView in them (for horizontal carousels). It works great for every cell except for one in which the tableView can't be scrolled (either in simulator or on device). To be more exact, the scroll is possible if the touch occurs on the UICollectionViewCell, but not if it occurs on the portion of the UITableViewCell that doesn't contain the UICollectionVIew.
I'd like to find out why that is, but I'm not sure how to go about debugging this. Is there a breakpoint, maybe a symbolic breakpoint, that I can set to understand where the touch events go?
I tried printing stuff in these 3 methods (from the cell itself) to see how and when they were called, but there are not called.
override func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
override func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
I am not sure posting code is relevant. It's a complex view with lots imbricated parts. I'd rather prefer any debugging technique that would be helpful in this situation.
Thank you very much!
EDIT
Here's more precisions:
There are no other gestures than the ones of the UITableView and UICollectionView.
My view hierarchy is like this:
|--- UITableViewCell --------------|
|-|---cell's content view -------|-|
|-|-|---container view --------|-|-|
|-|-|-|---labels ------------|-|-|-|
|-|-|-|---collection view ---|-|-|-|
|-|-|-|----------------------|-|-|-|
|----------------------------------|
The container view inside the cell's content view has its purpose. But I feel like it is that view that block the scroll gestures.
Apart from isUserInteractionEnable = true, is there anything else that could block gestures events to go up the superviews? This container view doesn't have any gestures attached to it.
I finally made it work by changing the background color of the container view from .clear to some gray.
I'm confused that it works, because transparent views (color: .clear) are supposed to participate in the event responder chain. But that being said, everything was set to .clear, from the container view to the tableView. So the color that was displaying was the viewController's background color.
I'm not sure if it was a bad setup on my part or a bug in UIKit, though. I'd guess it's on me? Anyway... I'm leaving this here in case somebody else runs into this issue.

Is it possible to make Tap Gesture Recognizer work if UIImageView it's connected to is under UIScrollView?

Please take a look on a screenshot
I have a UIImageView with Tap Gesture Recognizer connected to it ("Mans body image view"). When I tap this UIImage I want it to call a keyboard with colors so I can change skin color of this mans figure.
For now it does not work because on top of Mans body image view I have 4 scroll views which are responsible for hair, facial hair and clothing (tops and bottoms).
Is there any way I can make Tap Gesture Recognizer (under scroll views) to react on my taps?
Thank you
Make a UIScrollView subclass and implement
func gesture​Recognizer(UIGesture​Recognizer, should​Recognize​Simultaneously​With:​ UIGesture​Recognizer)
Asks the delegate if two gesture recognizers should be allowed to recognize gestures simultaneously.
func gestureRecognizer(_ gestureRecognizer: UIPanGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UISwipeGestureRecognizer) -> Bool {
return true
}
or use horizontal UICollectionView instead and implement delegate method didSelectItemAtIndexPath.

Custom Interactive Transition With Table View

I create custom push/pop interactive transition, but I can't understand how to handle this transition and table view scroll simultaneously.
For handle custom pop animation I add pan gesture
let pan = UIPanGestureRecognizer(
target: self,
action: #selector(didPan))
pan.delegate = self
view.addGestureRecognizer(pan)
Next I handle this pan
if recognizer.state == .began {
animator.interactive = true
navigationController!.popViewController(animated: true)
}
animator.handlePan(recognizer: recognizer)
Then inside animator conformed to UIPercentDrivenInteractiveTransition and UIViewControllerAnimatedTransitioning I do transform animation.
I try to pop navigation from VC_2 to VC_1 by pan gesture.
The problem is after adding pan gesture table view is not scrolling. I think it's because 2 gesture recognizers (my own and UIKit table view's recognizer). Actually I want my own pan interactively pop my VC_2 when table is scrolled up.
The only one idea was
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return tableView.contentOffset.y <= 0
}
But on pop table view offset changed strange: I mean table is scroll too much as pan gesture moved (move for 20px, offset changed for 500px some kind of that).
What is the best way to handle this? You can see such animation in Inbox app from Google - pop from e-mail to list.

Disabling user interaction for subview interferes with event handling for superview?

CustomView contains a subview, SubView, which implements a tap handler. However, for this implementation, SubView should ignore taps and let CustomView handle them.
The code below is supposed to achieve this, but setting userInteractionEnabled to false prevents taps on SubView from cascading to CustomView. Shouldn't CustomView still receive tap events if SubView has userInteractionEnabled set to false?
The Apple documentation says setting userInteractionEnabled to false causes events to get ignored, not that the view will swallow them so superviews don't receive them.
// CustomView class, which is a subclass of UIView
// Handle taps
let singleTap = UITapGestureRecognizer(target: self, action: #selector(doTap))
addGestureRecognizer(singleTap)
// Add SubView
view.insertSubview(SubView, atIndex: 0)
SubView.userInteractionEnabled = false
Updated code (doInit is called since buttonTapped is invoked on taps) but still not working:
class CustomButton : UIButton, UIGestureRecognizerDelegate {
private func doInit() {
...
// Handle taps
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(buttonTapped))
tapRecognizer.delegate = self
addGestureRecognizer(tapRecognizer)
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
SubView will still receive the taps, but it will ignore them and it will stop there. If a view has userInteractionEnabled set to false, it will block touches that are meant for parent views.
My guess is you want to add this gesture recognizer delegate:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool
This will allow your parent view to receive touches at the same time as your SubView.
Building on Laynemoseley's answer, this is the relevant bit from the Apple docs:
In iOS 6.0 and later,
default control actions prevent overlapping gesture recognizer
behavior. For example, the default action for a button is a single
tap. If you have a single tap gesture recognizer attached to a
button’s parent view, and the user taps the button, then the button’s
action method receives the touch event instead of the gesture
recognizer. This applies only to gesture recognition that overlaps the
default action for a control, which includes:
A single finger single tap on a UIButton, UISwitch, UIStepper,
UISegmentedControl, and UIPageControl. A single finger swipe on the
knob of a UISlider, in a direction parallel to the slider. A single
finger pan gesture on the knob of a UISwitch, in a direction parallel
to the switch. If you have a custom subclass of one of these controls
and you want to change the default action, attach a gesture recognizer
directly to the control instead of to the parent view. Then, the
gesture recognizer receives the touch event first. As always, be sure
to read the iOS Human Interface Guidelines to ensure that your app
offers an intuitive user experience, especially when overriding the
default behavior of a standard control.
It seems the only solution is to override the gesture recognition and allow simultaneous gestures to get recognized.

Resources