I have found many posts on managing gestureRecognizers, but I have not been able to find my answer, or rather get anything to work correctly.
I have a UISwipeGestureRecognizer set to down in my Main ViewController, and a PanGestureRecognizer on the x axis attached to an overlay view that pulls the overlay view over to overlay my main view from the right.
Swipe moves down, Pan is for Left to Right.
I want the swipe down to work on all the views, and it does, but when the user swipes down on my overlay view, it also triggers the UIPanGestureRecognizer, which hides the view.
I think this is because there is slight movement in the X direction when you swipe, so the Pan is getting triggered.
How do I get my UIPanGestureRecognizer to only fire if it is a definite Pan to the left or right, and not the trailing end of a swipe?
I have tried shouldRecognizeSimultaneouslyWithGestureRecognizer but it can't really work correctly. Currently they are all set to delegate self.
Any help would tremendously appreciated. Thank you!
The solution was placing
[panGesture requireGestureRecognizerToFail:swipeGesture];
in my viewController's viewDidLoad.
and implementing
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
in my ViewController.m.
I had it placed in my overlay view's viewDidLoad and .m, and that was never getting called. Silly me.
Related
UISwipeGestureRecognizer *swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(resignTextView)];
swipeGestureRecognizer.direction=UISwipeGestureRecognizerDirectionDown;
swipeGestureRecognizer.numberOfTouchesRequired=1;
[self.tableview addGestureRecognizer:swipeGestureRecognizer];
-(void)resignTextView
{
[textView resignFirstResponder];
}
I do not know Why resignTextView method not get called?
The tableview has a scrollview and you can get the delegates of it to get the scroll events.
Check the docs for the UIScrollViewDelegate protocol, and implement the -scrollViewDidScroll: or -scrollViewWillBeginDragging: methods as appropriate to your situation.
Just implement the methods and it works since the tableview delegates implements the underlying scroll delegates.
You are adding Swipe Gesture on Table View and in Downword direction. As you know Table View already has a Scroll and it scrolls upward and downward direction. If you add a Swipe Gesture in Downward direction. Then every time when you swipe in downward direction calls scroll methods of scroll and table moves scroll and device layer detect no swipe action, so ur method not called. In a very few situation when sometime there is less cell and table is not scrolling then try to check it again ur swipe method get called. Both swipe in downward direction and scrolling are not works properly in simultaneously.
you missed : after resignTextView
I have a UIScrollView and I'm implementing the viewForZoomingInScrollView: delegate method which returns a UIImage which the user can zoom and pan. I've also got some UIButtons as sub views of the UIScrollView which I'm using as annotations, like Google Maps.
The problem I'm having is that a lot of the UIImage can be obscured by the UIButtons when zoomed right out. When trying to pinch to zoom the UIButtons are receiving the touch event instead and the zoom is not happening. You end up having to carefully place your fingers in clear space to zoom.
I note the Google Maps app seems to work ok when there are lots of annotation views, you can still pinch.
I guess I need to priorities the touches, the UIScrollView needs to respond to pinches and pans, while the buttons just taps.
Anyone have experience of this?
I had this exact issue, and it was kind of weird. It wasn't due to any gesture recognizer on the button, it was the UIScrollView's pinch gesture recognizer that was being forwarded to the UIButton for some reason. Also, the UIButton was responding to certain UIGestureRecognizerDelegate calls (like -gestureRecognizerShouldBegin:) but not others (like -gestureRecognizer:shouldReceiveTouch:). It's like the UIScrollView was selectively forwarding some delegate calls to the UIButton and some not.
I finally found a very easy way to solve this issue:
yourScrollView.pinchGestureRecognizer.delaysTouchesBegan = YES;
yourScrollView.pinchGestureRecognizer.delaysTouchesEnded = YES;
Luckily, the default pinch gesture recognizer on UIScrollView's are publicly accessible, and the two delaysTouches property are NO by default. When you set them to YES, if the gesture recognizer could possibly or does recognize a pinch gesture even when it starts on top of a UIButton, it won't forward those touches to the UIButton, and the UIButtons will no longer interfere with the UIScrollView's zooming.
try this
UIGestureRecognizer* tapRecognizer = nil;
for (UIGestureRecognizer* recognizer in yourButton) {
if ( ![recognizer isKindOfClass:[UITapGestureRecognizer class]] ) {
[yourButton removeGestureRecognizer:recognizer];
break;
}
}
so you remove all the gesture recognizers from the button different from UITapGestureRecognizer
take a look at UIView's hitTest:withEvent: method. Inside that method youll need to check for which view you want to return. The view you return will be the one recieving the touches. for example, you can subclass the button and override that method to return the ImageView for your particular scenario.
I fixed this problem in a different way, but this might not be your case.
My buttons were added on an image view, the image view being added by itself to another container view. In the:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
... method, I returned the image view - bad. Changing to return my very first container view in hierarchy fixed the touches.
In my app, a top view has pan gesture recognizers to move the view to the left and to the right to reveal two other views underneath it. I'm trying to put a scrollview into the top view, but i can't seem to get it to scroll. I'm using storyboards for the scrollview but the gesture recognizer is implemented programmatically. I have the scroller insets set to exceed the size.
I'm thinking that maybe the gesture recognizers take priority over the scrollview's ability to scroll, but I can't find any way to check.. Unless I'm looking over something stupid, could someone please help with this?
UIScrollView implements a private UISwipeGestureRecognizer that does hold priority over any other gesture. But, it's one many people have overridden (it's fairly easy, just a for-in loop or two) to hook into it's events or provide their own subclass, which is the only way to get other gestures to recognize simultaneously. Actually, that's the name of the delegate method you need to implement, - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
I have a UIScrollView and a UITapGestureRecognizerwhich are both pretty basic, and have got them to work together in every way I need except one. The UIScrollView doesn't use any zooming, and the UITapGestureRecognizer just writes to the console for now.
I can get the UITapGestureRecognizer to write to the console when the UIScrollView is tapped, either while stationary or animating through setContentOffset, but I cannot get it to work when the UIScrollView is moving due to being swiped. When the UIScrollView is swiped and still in motion, the first tap after swiping will stop it moving, and then only the second tap is picked up by the UITapGestureRecognizer. I hope to get the first tap to both stop the UIScrollView from scrolling and also write to the console through the UITapGestureRecognizer.
I hope that my problem here is just through a gap in my knowledge of either the UIScrollView or UITapGestureRecognizer and there is just a Property to set to fix this, but so far no amount of reading has helped me with this issue. Any ideas on whether this is possible?
Edit: Thanks for the suggestions, please see below
Apologies, I don't think I explained myself very well. I realise the first movement on the stationary UIScrollView is a swipe (which in my case is just handled by the UIScrollView btw, not a gesture recognizer).
The problem is after swiping and releasing, if you tap while it is still in motion (and no other touch is in progress), it isn't picked up by the UITapGestureRecognizer, instead it is only picked up by the UIScrollView and stops the UIScrollView moving. If you let it decelerate then tap, this works fine. Also if it is moving due to an animation but not a swipe, the tap also works fine.
This is the same when using a `UILongPressGestureRecognizer' which is what I want to use ideally, but thought if I can't get it to work with a tap, I have no chance with that!
Sounds like you need to implement the
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
delegate method for your UITapGestureRecognizer and return YES for the UIGestureRecognizers you want to interpret tap gestures in sync with.
e.x.
// Somewhere in your code...
UIGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
tap.delegate = self;
[self.view addGestureRecognizer:tap];
[tap release];
// And the delegate method...
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
n.b. You may want to be more discriminating in deciding which UIGestureRecognizer(s) you wish to work in sync with. The example will work with every other gesture recognizer.
The problem is that when the user taps the first time to scroll the UIScrollView, this is not a tap, it is a swipe. It would be considered a tap only when the user taps and releases the finger almost at the same position (without dragging the finger through the screen).
What you could do to solve your problem is: use the UIScrollView delegate method scrollViewWillBeginDragging and then you would know when the user just tapped and will start dragging the scrollview, and also add the UITapGestureRecognizer for those real taps.
Today on my creative time I did some quite comprehensive research on how to steal touches from a UIScrollView and send them instantly to a specific subview, while maintaining the default behavior for the rest of the scroll view. Consider having a UIPickerView inside of a UITableView. The default behavior is that if you drag your finger over the picker view, the scroll view will scroll and the picker view will remain unchanged.
The first thing I tried was to override
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
and simply not allow the UIScrollView to cancel touches inside the picker view. This works, but it has an unpleasant side effect. You would like the picker view to respond immediately and thus you will have to set delaysContentTouches to NO. The problem is that you don't want the rest of the table view to respond immediately, because if it does the table view cell will always get highlighted for a few milliseconds before the scrolling starts.
The second thing I tried was to override
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
because I had read that the scroll view always returns itself, so that it will "steal" the touches from its subviews and later send them to the subview if they weren't of interest to the scroll view. However, this isn't true anymore. UIScrollView's default implementation of hitTest:withEvent: actually returns the subview that should receive the touch. Instead it uses gesture recognizers to intercept the touches.
So the third thing I attempted was to implement my own gesture recognizer and cause it to fail if the touch was outside of the picker view and otherwise succeed. Then I set all the scroll view's gesture recognizers to fail unless my gesture recognizer failed using the following code:
for (UIGestureRecognizer * gestureRecognizer in self.tableView.gestureRecognizers)
{
[gestureRecognizer requireGestureRecognizerToFail:myRecognizer];
}
This does in fact steal the touches from the scroll view, but the picker view never receives them. So I though maybe I could just send all the touches that my gesture recognizer receives using this code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
[touch.view touchesBegan:touches withEvent:event];
}
The above code is a simplified version. I also make sure that the view is a picker view (or one of it's subviews) and set the appropriate state for the gesture recognizer as I mentioned above. I also did the same for canceled, ended and moved. However, the picker view was still not responding.
I also tried one last thing before returning to my regular work. During my extensive googling I read that nested UIScrollViews just magically worked since 3.x, so I tried putting my picker view inside a nested UIScrollView and set the following properties on it:
scrollView.delaysContentTouches = NO;
scrollView.canCancelContentTouches = NO;
As one would expect the outer scroll view didn't treat the inner scroll view any different than it treated the picker view, so the inner scroll view did not receive the touches. I thought that it was a long shot, but it was simple enough to implement, so I thought it was worth to give it a shot.
What I know is that UIScrollView has a gesture recognizer named UIScrollViewDelayedTouchesBeganGestureRecognizer that intercepts the touches and sends them to the appropriate subview after 150 (?) ms. I'm thinking that I should be able to write a similar recognizer that causes the scroll view's default recognizers to fail and instead of delaying the touches immediately sends them to the picker view. So if anyone knows how to write such a recognizer please let me know and if you have any other solution to the problem, you're very welcome share that as well.
Thank you for reading through the whole question and even if you don't know the answer you could still upvote the question so that it gets more attention (hopefully from someone that can answer it). Thanks! :)
Sometimes you have to ask the question before you can find the answer. Dan Ray had a similar problem and solved it with a very different solution.
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if ([result.superview isKindOfClass:[UIPickerView class]])
{
self.scrollEnabled = NO;
}
else
{
self.scrollEnabled = YES;
}
return result;
}
I've tested the code and it works fine for me as well. However, this is not really stealing touches from the scroll view, so if anyone knows how to actually steal touches that would be great.
Source:
UIPickerView inside UITableView.tableFooterView doesn't receive drag touches
A bit late, but I found this solution:
http://www.cocoanetics.com/2010/06/hacking-uiscrollview-gesture-recognizers/
Works for me
I'm also late to the party, but for newcomers, if you're just looking to flat out ignore swipes on the scroll view, what worked for me was to add a pan gesture recognizer to the view I want ignoring the swipes, like this:
let panGesture = UIPanGestureRecognizer()
panGesture.cancelsTouchesInView = false
myView?.addGestureRecognizer(panGesture)