Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIPageViewController Gesture recognizers

I have a UIPageViewController load with my Viewcontroller.

The view controllers have buttons which are overridden by the PageViewControllers gesture recognizers.

For example I have a button on the right side of the viewcontroller and when you press the button, the PageViewController takes over and changes the page.

How can I make the button receive the touch and cancel the gesture recognizer in the PageViewController?

I think the PageViewController makes my ViewController a subview of its view.

I know I could turn off all of the Gestures, but this isn't the effect I'm looking for.

I would prefer not to subclass the PageViewController as apple says this class is not meant to be subclassed.

like image 574
Rich86man Avatar asked Oct 17 '11 02:10

Rich86man


8 Answers

Here is another solution, which can be added in the viewDidLoad template right after the self.view.gestureRecognizers = self.pageViewController.gestureRecognizers part from the Xcode template. It avoids messing with the guts of the gesture recognizers or dealing with its delegates. It just removes the tap gesture recognizer from the views, leaving only the swipe recognizer.

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

// Find the tap gesture recognizer so we can remove it!
UIGestureRecognizer* tapRecognizer = nil;    
for (UIGestureRecognizer* recognizer in self.pageViewController.gestureRecognizers) {
    if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) {
        tapRecognizer = recognizer;
        break;
    }
}

if ( tapRecognizer ) {
    [self.view removeGestureRecognizer:tapRecognizer];
    [self.pageViewController.view removeGestureRecognizer:tapRecognizer];
}

Now to switch between pages, you have to swipe. Taps now only work on your controls on top of the page view (which is what I was after).

like image 199
Pat McG Avatar answered Nov 11 '22 05:11

Pat McG


You can override

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
 shouldReceiveTouch:(UITouch *)touch

to better control when the PageViewController should receive the touch and not. Look at "Preventing Gesture Recognizers from Analyzing Touches" in Dev API Gesture Recognizers

My solution looks like this in the RootViewController for the UIPageViewController:

In viewDidLoad:

//EDITED Need to take care of all gestureRecogizers. Got a bug when only setting the delegate for Tap
for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
    gR.delegate = self;
}

The override:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    //Touch gestures below top bar should not make the page turn.
    //EDITED Check for only Tap here instead.
    if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
        CGPoint touchPoint = [touch locationInView:self.view];
        if (touchPoint.y > 40) {
            return NO;
        }
        else if (touchPoint.x > 50 && touchPoint.x < 430) {//Let the buttons in the middle of the top bar receive the touch
            return NO;
        }
    }
    return YES;
}

And don't forget to set the RootViewController as UIGestureRecognizerDelegate.

(FYI, I'm only in Landscape mode.)

EDIT - The above code translated into Swift 2:

In viewDidLoad:

for gr in self.view.gestureRecognizers! {
    gr.delegate = self
}

Make the page view controller inherit UIGestureRecognizerDelegate then add:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if let _ = gestureRecognizer as? UITapGestureRecognizer {
        let touchPoint = touch .locationInView(self.view)
        if (touchPoint.y > 40 ){
            return false
        }else{
            return true
        }
    }
    return true
}
like image 50
jramer Avatar answered Nov 11 '22 06:11

jramer


I had the same problem. The sample and documentation does this in loadView or viewDidLoad:

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

This replaces the gesture recognizers from the UIViewControllers views with the gestureRecognizers of the UIPageViewController. Now when a touch occurs, they are first sent through the pageViewControllers gesture recognizers - if they do not match, they are sent to the subviews.

Just uncomment that line, and everything is working as expected.

Phillip

like image 4
Phillip Avatar answered Nov 11 '22 04:11

Phillip


Setting the gestureRecognizers delegate to a viewController as below no longer work on ios6

for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
    gR.delegate = self;
}

In ios6, setting your pageViewController's gestureRecognizers delegate to a viewController causes a crash

like image 3
noRema Avatar answered Nov 11 '22 04:11

noRema


In newer versions (I am in Xcode 7.3 targeting iOS 8.1+), none of these solutions seem to work.

The accepted answer would crash with error:

UIScrollView's built-in pan gesture recognizer must have its scroll view as its delegate.

The currently highest ranking answer (from Pat McG) no longer works as well because UIPageViewController's scrollview seems to be using odd gesture recognizer sub classes that you can't check for. Therefore, the statement if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) never executes.

I chose to just set cancelsTouchesInView on each recognizer to false, which allows subviews of the UIPageViewController to receive touches as well.

In viewDidLoad:

guard let recognizers = self.pageViewController.view.subviews[0].gestureRecognizers else {
    print("No gesture recognizers on scrollview.")
    return
}

for recognizer in recognizers {
    recognizer.cancelsTouchesInView = false
}
like image 3
Hunter Monk Avatar answered Nov 11 '22 05:11

Hunter Monk


I used

for (UIScrollView *view in _pageViewController.view.subviews) {
    if ([view isKindOfClass:[UIScrollView class]]) {
        view.delaysContentTouches = NO;
    }
}

to allow clicks to go through to buttons inside a UIPageViewController

like image 2
Adam Johns Avatar answered Nov 11 '22 04:11

Adam Johns


In my case I wanted to disable tapping on the UIPageControl and let tapping being received by another button on the screen. Swipe still works. I have tried numerous ways and I believe that was the simplest working solution:

for (UIPageControl *view in _pageController.view.subviews) {
    if ([view isKindOfClass:[UIPageControl class]]) {
        view.enabled = NO;
    }
}

This is getting the UIPageControl view from the UIPageController subviews and disabling user interaction.

like image 2
Leto Baxevanaki Avatar answered Nov 11 '22 05:11

Leto Baxevanaki


Just create a subview (linked to a new IBOutlet gesturesView) in your RootViewController and assign the gestures to this new view. This view cover the part of the screen you want the gesture enable.

in viewDidLoad change :

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

to :

self.gesturesView.gestureRecognizers = self.pageViewController.gestureRecognizers;
like image 1
2 revs Avatar answered Nov 11 '22 05:11

2 revs