UIPanGestureRecognizer conflict with scrollview - ios

I'm trying to add a pan gesture recognizer to a view containing a scrollview, but I guess I've problems with priorities.
My global UIView has a UIPanGestureRecognizer set like this:
_bottomPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(bottomPanGestureDetected:)];
_bottomPanGestureRecognizer.minimumNumberOfTouches = 2;
_bottomPanGestureRecognizer.maximumNumberOfTouches = 2;
_bottomPanGestureRecognizer.delaysTouchesBegan = NO;
_bottomPanGestureRecognizer.delaysTouchesEnded = NO;
I want to recognize this gesture to display another view from the bottom with some sort of pinch down-to-up.
The problem is that the scrollview is recognizing its own pan gesture before mine.
So I tried to delay it thanks to:
[_scrollView.panGestureRecognizer requireGestureRecognizerToFail:_bottomPanGestureRecognizer];
And it's working, the scrollview event is fired after my two finger down to up recognizer, but the problem is now when I only use one finger to scroll in the scrollview, the scroll works after a small delay.
I would like to have no delay for this event, is this possible? Any idea welcomed!
Cheers.
Cyril

In case it isn't solved yet, i solved the problem for me.
I added a UIPanGestureRecognizer to a UIScrollView to detect two finger pan gestures and the default UIScrollView behaviour (scrolling to something) still workes.
So what i did is to add the UIPanGestureReconizer to the UIScrollView:
UIPanGestureRecognizer *pangestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(displayReloadIndicator:)];
pangestureRecognizer.minimumNumberOfTouches = 2;
pangestureRecognizer.delegate = self;
[self.scrollView addGestureRecognizer:pangestureRecognizer];
[pangestureRecognizer release];
After this i added the code:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
After this, i implemented the pan gesture recognizers action method.
- (void) displayReloadIndicator:(UIPanGestureRecognizer*) panGestureRecognizer {
UIGestureRecognizerState gestureRecognizerState = gestureRecognizer.state;
CGPoint translation = [gestureRecognizer translationInView:self.scv_bibgesamt];
if (gestureRecognizerState == UIGestureRecognizerStateBegan) {
// create a UIView with all the Pull Refresh Headers and add to UIScrollView
// This is really much lines of code, but its simply creating a UIView (later you'll find a myRefreshHeaderView, which is my base view) and add UIElements e.g. UIActivityIndicatorView, a UILabel and a UIImageView on it
// In iOS 6 you will also have the possibility to add a UIRefreshControl to your UIScrollView
}
else if (gestureRecognizerState == UIGestureRecognizerStateEnded
|| gestureRecognizerState == UIGestureRecognizerStateCancelled) {
if (translation.y >= _myRefreshHeaderView.frame.size.height + 12) { // _myRefreshHeaderView is my baseview
//so the UIScrollView has been dragged down with two fingers over a specific point and have been release now, so we can refresh the content on the UIScrollView
[self refreshContent];
//animatly display the refresh view as the top content of the UIScrollView
[self.scrollView setContentOffset:CGPointMake(0, myRefreshHeaderView.frame.size.height) animated:YES];
}
else {
//the UIScrollView has not been dragged over a specific point so don't do anything (just scroll back to origin)
[self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
//remove the view (because it's no longer needed)
[_myRefreshHeaderView removeFromSuperview];
}
}
UPDATE:
In case you may wish to integrate the swipe back functionality from your navigationcontroller, you should integrate following code:
- (void) viewDidLoad {
[super viewDidLoad];
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
//setup view controller
}
and
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (gestureRecognizer == _panGestureRecognizer
&& [self.navigationController.viewControllers count] > 1) {
CGPoint point = [touch locationInView:self.view.window];
if (point.x < 20
|| point.x > self.view.window.frame.size.width - 20) {
return NO;
}
}
return YES;
}

Implement panRecognizer delegate to enable simultaneously recognize UIScrollView UIGestureRecognizer
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (_panRecognizer == gestureRecognizer) {
if ([otherGestureRecognizer.view isKindOfClass:UIScrollView.class]) {
UIScrollView *scrollView = (UIScrollView *)otherGestureRecognizer.view;
if (scrollView.contentOffset.x == 0) {
return YES;
}
}
}
return NO;
}

SWIFT 4
If using a scrollView you can use below, it is only a panGestureRecognizer when a scrollView is dragging at the very top:
translation.y > 0 means you are moving from top to bottom and
locationInScrollView.y < 500.0 means you end dragging at 500 or less (you can customize this) to prevent that the refresh is done in middle or bottom of the scroll.
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
let locationInScrollView = scrollView.panGestureRecognizer.location(in: scrollView)
if translation.y > 0 && locationInScrollView.y < 500.0 {
print("scrollView refresh: Y: \(locationInScrollView.y)")
setupUI()
}
}

Related

Allow vertical scrolling of UIScrollView with Charts on iOS

I have a LineChartView from the Charts framework within a UIScrollView. Values on the chart can be highlighted by panning around to see more details about the specific data points.
These are my goals for the overall behaviour of panning within my view controller:
The scroll view should scroll if the user pans anywhere on the view controller. Even on the chart view, providing that the direction is vertical.
The chart should only highlight values when the user scrolls on it horizontally and the scroll view should not react in this case.
Currently, the scroll view ignores scrolling when interacting with the chart completely and the chart reacts to vertical panning, which it should not, so while I'm close to a solution, it's not quite there.
Here is my current implementation...
- (void)viewDidLoad {
[super viewDidLoad];
NSArray<UIGestureRecognizer *> *gestureRecognizers = self.lineChartView.gestureRecognizers;
for (UIGestureRecognizer *gestureRecognizer in gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
gestureRecognizer.delegate = self;
self.lineChartViewPanGestureRecognizer = (UIPanGestureRecognizer *)gestureRecognizer;
}
}
UIPanGestureRecognizer *lineChartViewPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleLineChartViewPanGestureRecognizer:)];
[self.lineChartView addGestureRecognizer:lineChartViewPanGestureRecognizer];
}
- (void)handleLineChartViewPanGestureRecognizer:(UIPanGestureRecognizer*)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
[self.lineChartView highlightValues:NULL];
}
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (gestureRecognizer == self.lineChartViewPanGestureRecognizer && otherGestureRecognizer == self.scrollView.panGestureRecognizer) {
UIPanGestureRecognizer* panGestureRecognizer = (UIPanGestureRecognizer *)otherGestureRecognizer;
CGPoint velocity = [panGestureRecognizer velocityInView:self.view];
return fabs(velocity.y) > fabs(velocity.x);
}
return YES;
}
Anyone have any suggestions as to how I can keep the scroll view scrolling when the user pans vertically on the chart (which should itself ignore such panning)?

UIPanGestureRecognizer interception on SideMenu and UIPageViewController

I have leftMenu appears when swipe to right, and I want my tab changes when swipe to left whole page.
Their UIPanGestureRecognizer intercepts, I have tried disabling bouncing of UIScrollView of UIPageViewController, tried requireGestureRecognizerToFail:
But I could not make it both of them works. What I want is when it is swiped to to right side menu appears. When it is swiped to left if sidemenu is open it will close, if not, page will change.
My sample code is as follows;
for(UIScrollView *view in self.pageViewController.view.subviews)
{
if ([view isKindOfClass:[UIScrollView class]])
{
UIScrollView *scrollView = (UIScrollView *)view;
AppDelegate *appDel = [UIApplication sharedApplication].delegate;
UIPanGestureRecognizer* panGestureRecognizer = scrollView.panGestureRecognizer;
[panGestureRecognizer addTarget:self action:#selector(move:)];
[panGestureRecognizer requireGestureRecognizerToFail:appDel.slidingViewController.panGesture];
}
}
Thanks in advance
Something like the following should work (warning: untested!)
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panRecognized:)];
panRecognizer.delegate = self;
for (UIGestureRecognizer *recognizer in self.pageViewController.gestureRecognizers)
[recognizer requireGestureRecognizerToFail:panRecognizer];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)recogniser
{
if (self.leftMenuShowing)
return YES;
else
{
UIPanGestureRecognizer *panRecognizer = (id)recognizer;
CGPoint translation = [panRecognizer translationInView:self.view];
return (translation.x > 0);
}
}
- (void)panRecognized:(UIPanGestureRecognizer *panRecognizer)
{
if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged)
{
// if pan is to left, move leftMenu offscreen
// if pan is to right, move leftMenu onscreen
}
else
{
// if leftMenu is mostly onscreen, animate completely onscreen
// if leftMenu is mostly offscreen, animate completely offscreen
}
}

Customizing UIPageViewController gesture recognizers Scroll behavior

I want to implement -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch gesture delegate in pageviewcontroller.m file but i can't get any gesture as:
NSArray *temp = self.gestureRecognizers;
NSLog(#"count %i",temp.count); // this logs count 0
NSArray *temp = self.view.gestureRecognizers;
NSLog(#"count %i",temp.count); // this also logs count 0
for (UIGestureRecognizer *gR in temp) {
gR.delegate = self;
}
in above code self is pointing towards pageviewcontroller.
therefore i can't assign delegate to pageviewcontroller gestures.
Edited Part:
OK I got it, I am not getting any gesture object because of uipageviewscroll style.
But I have a problem I need to disable pageviewcontroller pan gesture and need to scroll the pageviewcontroller from two buttons, like if user try to pan, and it's starting point is inside my uibuttons frame then pageviewcontroller should scroll otherwise not.
I am using transitionStyle UIPageViewControllerTransitionStyleScroll.
Any solution for this...
Thanks in advance
After trying so many hacks, I finally got solution.
What i did, first got the pageviewcontroller scorll view in a property
for (UIView *view in mypageviewcontroller.view.subviews) {
if([view isKindOfClass:[UIScrollView class]])
{
pagescrollview= (UIScrollView *)view;
}
}
Then assigned pan gesture to pageviewcontroller scorllview and gesture delegate.
UIPanGestureRecognizer* g1 = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(gesture)];
[g1 setDelegate:self];
[pagescrollview addGestureRecognizer:g1];
Then in gesture delegate , i checked whether the gesture starts from my desire points, if starts from a location that should scroll pageviewcontroller then this delegate method should return no to enable original pagescorllview gesture to receive pan gesture.
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint touchPoint = [touch locationInView:self.view];
if(floor(NSFoundationVersionNumber)<=NSFoundationVersionNumber_iOS_6_1)
touchPoint.y -=44;
else
touchPoint.y -=64;
PGNavigationController *nav =ViewControllerArray[VisiblePageindex];
PageContentVC *pagecontentvc = ((PageContentVC *) nav.visibleViewController);
if (touchPoint.y > pagecontentvc.leftpanalbtn.frame.origin.y && (pagecontentvc.leftpanalbtn.frame.size.height+pagecontentvc.leftpanalbtn.frame.origin.y )>touchPoint.y && touchPoint.x >pagecontentvc.leftpanalbtn.frame.origin.x
&& touchPoint.x<(pagecontentvc.leftpanalbtn.frame.origin.x+pagecontentvc.leftpanalbtn.frame.size.width)) {
return NO;
}
else if (touchPoint.y > pagecontentvc.rightpanalbtn.frame.origin.y && (pagecontentvc.rightpanalbtn.frame.size.height+pagecontentvc.rightpanalbtn.frame.origin.y )>touchPoint.y && touchPoint.x >pagecontentvc.rightpanalbtn.frame.origin.x
&& touchPoint.x<(pagecontentvc.rightpanalbtn.frame.origin.x+pagecontentvc.rightpanalbtn.frame.size.width))
{
return NO;
}
if( touchPoint.y>282 && touchPoint.x>118 &&touchPoint.y<282+75 && touchPoint.x < 118+85)
{
return NO;
}
// else if()
}
return YES;
}
hope it help someone else.

Capture only UIView 2 finger UIPanGestureRecognizer

I have a couple of UIScrollViews in my view controller. I want to overlay a view that captures a 2 finger swipe via UIPanGestureRecognizer which will not record the UIScrollView swipe gestures.
When I put a transparent view over my content with a 2 finger pan gesture, my taps and 1 finger swipes are not detected. I tried overwriting the pointInside: method to return NO
but then it doesn't record my 2 finger swipe.
The effect is similar to the 4 finger swipe to change apps.
You don't need an overlay view.
First implement UIPanGestureRecognizer that will handle 2 finger pan and assign it to your view that contains UIScrollViews
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self
action:#selector(handlePan:)];
panGestureRecognizer.delegate = self;
panGestureRecognizer.minimumNumberOfTouches = 2;
panGestureRecognizer.maximumNumberOfTouches = 2;
[self.view addGestureRecognizer:panGestureRecognizer];
Use UIGestureRecognizerDelegate to handle 2 finger pan with UIScrollView pan gesture
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
And finally you are able to handle 2 fingers pan
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer
{
NSLog(#"pan");
}
If you want to stop scrolling UIScrollView when two finger pan is detected you can disable and enable UIScrollView pan recognizers
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer
{
if(gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
_scrollView.panGestureRecognizer.enabled = NO;
}
if(gestureRecognizer.state == UIGestureRecognizerStateEnded)
{
_scrollView.panGestureRecognizer.enabled = YES;
}
NSLog(#"pan");
}
If you don't really need the overlay you can solve this with just gesture recognizers. I wrote this up as a test:
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
_scrollView.contentSize = CGSizeMake(self.view.bounds.size.width * 2, self.view.bounds.size.height);
UIView *green = [[UIView alloc] initWithFrame:self.view.bounds];
[green setBackgroundColor:[UIColor greenColor]];
UIView *blue = [[UIView alloc] initWithFrame:CGRectOffset(self.view.bounds, self.view.bounds.size.width, 0)];
[blue setBackgroundColor:[UIColor blueColor]];
[_scrollView addSubview:green];
[_scrollView addSubview:blue];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(twoFingerPan:)];
[pan setMinimumNumberOfTouches:2];
[pan setMaximumNumberOfTouches:2];
[pan setDelaysTouchesBegan:YES];
[_scrollView addGestureRecognizer:pan];
[self.view addSubview:_scrollView];
}
- (void)twoFingerPan:(UIPanGestureRecognizer *)gesture {
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
self.scrollView.scrollEnabled = NO;
break;
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateFailed:
self.scrollView.scrollEnabled = YES;
break;
default:
break;
}
NSLog(#"2 Fingers!");
}
I get the twoFingerPan: call back for when 2 fingers are used. The scroll view's panGestureRecognizer is still working at that point so I disable scrolling on the scroll view to handle the 2 finger pan. I've found this method work's pretty well. One sort of wonky thing is if the scroll view is decelerating the 2 finger gesture recognizer isn't called. Hope that helps!

UIPageController and gesture recognizers

I am using UIPageController to implement a page based navigation application. I would like to enable the swipe gestures only at the margin of the page and prevent that from the inner content view. The page has a margin of 20 pixels:
contentView.frame = CGRectInset(self.view.frame, 20, 20);
Upon recognising a gesture you should be able to retrieve the locationInView and then if this is an acceptable value proceed, otherwise not.
First add in the class interface.
#interface MyPageViewController :UIViewController<UIPageViewControllerDelegate, UIGestureRecognizerDelegate>
Then in viewDidLoad add
for (UIGestureRecognizer *recognizer in self.pageViewController.gestureRecognizers) {
recognizer.delegate = self;
}
Then implement shouldReceiveTouch method
#pragma mark - UIGestureRecognizer delegate methods
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
CGPoint touchPoint = [touch locationInView:self.pageViewController.view];
CGRect innerRect = CGRectInset(self.pageViewController.view.frame, 40, 40);
if (CGRectContainsPoint(innerRect, touchPoint)) {
return NO;
}
return YES;
}

Resources