How to change/break Responder Chain? - ios

In UIViews, the next responder is its superview by default. In my project, there's a scroll view and a small uiview as scroll view's child view. I want if I touch in the small view, the scroll view shouldn't move. Let the responder chain break at the child view.
I am trying by inheriting the child view, and override the - (UIResponder *)nextResponder method to return nil. But it has no effect.
Update:
It seems that the child view didn't response to the Pan Gesture at all. When I scroll in child view, the - (UIResponder *)nextResponder method never called, but when I tap it, the method is called.

Gesture recognisers and the classic responder chain are mutually exclusive within the same view subtree.
-nextResponder isn't called because events are taking the gesture recognition path rather than the responder chain path.
Just use a gesture recogniser to do whatever event handling you're trying to achieve in the small view.

Related

Preventing unhandled touch events on a child view controller from passing through to container view

I have a container view controller managing its own full-screen content view, with several gesture recognizers attached. A child view controller can be overlaid over a portion of the screen; its root view is a UIView providing the opaque background color, which is covered by a UIScrollView, which in turn contains a complex view hierarchy of stack views, etc.
Scrolling in the child works correctly, as well as any user interactions with its subviews. The problem I'm having is that any taps or other non-scrolling gestures on the the scroll view itself (i.e. not inside any of its subviews) fall through the empty UIView behind it and are unexpectedly handled by the gesture recognizers on the root view of the parent (container) controller. I want those touches to be swallowed up by the child's background view so that they are ignored/cancelled.
My first thought was to override nextResponder on the child VC to return nil, assuming that would prevent touch events from passing to the superview. No success there, so I tried overriding the touch handling methods (touchesBegan: etc.) on the child controller, but they never get called. Then I substituted a simple UIView subclass to be the root view of my child controller, likewise trying both of those approaches there instead. Again returning nil for nextResponder has no effect, and the touch methods never get called.
My responder chain looks to be set up exactly as I would expect: scroll view --> child VC's root view --> child VC --> parent's root view --> parent VC. That makes me think my controller containment is set up correctly, and makes me suspect that the gesture recognizers on the parent's root view are somehow winning out over the responder chain in a way that I don't understand.
This seems like it should be easy. What am I missing? Thanks!
I think I understand better what's going on here thanks to this very helpful WWDC video.
Given an incoming touch, first the system associates that touch with the deepest hit-tested view; in my case that's the UIScrollView. Then it apparently walks back up the hierarchy of superviews looking for any other attached recognizers. This behavior is implied by this key bit of documentation:
A gesture recognizer operates on touches hit-tested to a specific view and all of that view’s subviews.
The scroll view has its own internal pan recognizer(s), which either cancel unrecognized touches or possibly fall back on responder methods that don't happen to forward touches up the responder chain. That explains why my responder methods never get called, even when my own recognizers are disabled.
Armed with this information, I can think of a few possible ways to solve my problem, such as:
Use gesture delegate methods to ignore touches if/when the associated view is under a child controller.
Write a "null" gesture recognizer subclass that captures all touches and ignores them, and attach that to the root view of the child controller.
But what I ended up doing was simply to rearrange my view hierarchy with a new empty view at the top, so that my child controller views can be siblings of the main content view rather than its subviews.
So the view hierarchy changes from this:
to this:
This solves my problem: my gesture recognizers no longer interact with touches that are hit-tested to the child controller's views. And I think it better captures the conceptual relationships between my app's controllers, without requiring any additional logic.

Drag & Drop in MAster-DetailView Controller in iOS

I want to drag from the masterview tableview Cell to DetailView.
I try with touchesBegan & touchesEnded method but not working.
Can you please help me for this?
Thank you
This is actually not easy. You can start by
Adding a pan gesture recognizer (UIPanGestureRecognizer) to the root view controller's view (UIWindow.keyWindow!.rootViewController!.view).
When the pan starts (i.e. user touches the screen), loop through the master view's table view's visible cells to see if the point is inside any cell by using UIView's convertPoint:fromView:. You may need to adjust timing to avoid interfering with table view's scrolling and tapping.
If a cell contains the pan's point, create an "indicator view" (that shows that user is dragging) and add it to the root view controller's view, on top of everything else and position it properly, e.g. under user's finger.
When the pan changes (i.e. user moves his finger), update the indicator view's location.
When the pan ends (i.e. user releases his finger), check if the point is inside the detail view and do whatever you need to do.
Check this out. It demonstrates how to do drag drop within a view. Your problem is more complicated as it involves different view controllers, hence the touch handling must be done at a level higher than both master and detail view controllers.
Why are you using touchesBegan & touchesEnded methods? If you have the tableView you should use didSelectRowAtIndexPath delegate method. And you can also use segues if you are using storyboards.

Propagating specific touch event to view underneath

I want to implement a feature similar to this:
View A and View B are all child views of the same parent UIView. View A and View B are sibling views. View A is the same frame size as the parent view. View B is only half of the size and it's above View A. I want to capture swipe events/gestures on View B, but still propagate other events/gestures (e.g. Tap) from View B to View A. So user can still interact with View A (e.g. tap on the button on View A) while View B is only capturing swipe gestures.
I have tried implementing following methods on View B
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
but seems like it's all or nothing approach. as I cannot detect in these methods if the event is a tap or a swipe. And I didn't find any API which can programatically trigger a tap on a UIView (not calling touch related methods).
You should move your rear sibling view to be a subview of the front view as this effective looks the same but makes the UIResponder chain behave correctly. UIGestureRecognizer can then do the rest of the work for you. Specifically it has a property, CancelsTocuhesInViews that you can turn off so it doesn't eat touch events and it has a delegate with methods that can allow you to specify the conditions under which a UIGestureRegonizer should fire. Implement the UIGestureRecognizerDelegate methods that you want to make your logic work. For instance if you want the overlay view to handle a tap only if a bunch of other gestureRecognizers don't handle the tap first then you implement gestureRecognizer(_:shouldRequireFailureOf:) and have it return true for every other gesture recognizer you want to go first; your gesture recognizer will then only fire when all of these other gesture recognizers have failed to handle the tap.
If I understand you correctly, what you want to do is usually done using UIGestureRecognizer objects and its subclasses. You can make one gesture recognizer (i.e. a swipe (or was it pan?) gesture recognizer) depend on another one (one for taps).
You can tell one gesture recognizer to only trigger when another has failed to detect its gesture. I haven't had to do that yet, so can't give you more detail, but I'd look into those classes and see if there's a way to chain them so it handles a swipe, and just does not handle taps. If you don't handle a tap, it should automatically go to any subview that will handle it, I think.

Forwarding touches to UINavigationController Interactive Pop Gesture Not completely Working

I'm trying to enable interactive pop gesture recogniser on my keyboard's accessory view. It does not work by default.
I passed an interactive pop gesture recogniser reference to my accessory view in order to forward its touch events to the recogniser
It particularly works: the navigation bar's title gets changed and the background of the accessory view reveals the previous view controller's view as if the transition did start. But the top view itself remains in place even if the gesture recogniser completes tracking.
I also tried to forward touch events to the navigation controller itself, to its view, to its top view controller and to their window. Nothing changed even after forwarding to all of them simultaneously
Any ideas what is missing?
It looks like it is not possible to reuse touch event instances in the responder chain. Once the sendEvent: on UIWindow is called, there is already a certain view owning the touch, so there is no cense in forwarding the UIEvent instances to other views or their gesture recognisers.
However, the owning view can forward events to its nextResponder()s (e.g.: one of the gesture recognisers attached to this view or to one of the subviews of the view)
The only chance to forward touches to another view (from another view hierarchy) or another view's gesture recognisers is before the UITouch object creation: i.e. on the UIWindow level during the hitTest:withEvent: method invocation, which calls the pointInside:withEvent: method
Anyway I'm not sure whether it is possible to forward touches from one UIWindow to another. Will update the answer later
http://www.russbishop.net/uitouchtypestylus?utm_campaign=iOS%2BDev%2BWeekly&utm_medium=email&utm_source=iOS_Dev_Weekly_Issue_225
http://smnh.me/hit-testing-in-ios/

Prevent UIScrollView's UIPanGestureRecognizer from blocking UIScreenEdgePanGestureRecognizer

I have a UIScrollView that fills the screen on one page of my app, but I want to allow the user to pan from the edge of the screen to reveal a view behind it. The problem is that the UIScrollView steals the touches from my UIScreenEdgePanGestureRecognizer at the edge of the screen. Unfortunately, I can't access the UIScreenEdgePanGestureRecognizer from the view controller that has the UIScrollView (and vice-versa), so I am not able to use the method requireGestureRecognizerToFail: because I cannot able to specify which gesture recognizer should be allowed to fail. The view controller with the scroll view is a child view controller of a container view controller that has the screen edge pan gesture recognizer attached to one of the container view controller's own views.
I'm also unable to use the delegate method
-(BOOL)gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
because the UIScrollView won't allow me to set my view controller as the delegate of the scroll view's UIPanGestureRecognizer.
How can I prevent the scrollview from stealing the edge pan touches from my own gesture recognizer?
Unfortunately, creating this behavior can be a real P*** i* t** A**.
Fortunately, creating this behavior is possible using the UIGestureRecognizer Delegate even if you can't access one GestureRecognizer directly.
-(BOOL)gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
-(BOOL)gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
-(BOOL)gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:
The second parameter ('otherGestureRecognizer') passed in the delegate methods holds the UIScrollView's PanGestureRecognizer (or private Apple - subclasses) when your gestureRecognizer 'collides' with the scrollView's.
So simply set your UIScreenEdgePanGestureRecognizer's delegate to reply to the delegate methods.
The naming of these two methods is very suboptimal and to be honest, i don't really know what the correct return values for your case are.
I just had this problem yesterday and i solved it by brutal trial & error.
Im my case, returning NO from both shouldRequireToFail and shouldBeRequiredToFail methods and YES from the simultaneous-method solved my problem.
Note: Returning NO from both methods changed the behavior compared to not
even implementing the methods at all. Even though the documentation says
the default return value is NO.
However, ANY GestureRecognizer behavior can be achieved by using the delegate methods. But as i said above, the naming of the methods is just very confusing. + there is as much as NO useful documentation for these methods.
This can be done without having to set the screen pan gesture's delegate to your view controller.
[scrollView.panGestureRecognizer requireGestureRecognizerToFail:screenPanGesture];

Resources