UIPageViewController Gesture recognizers
Solution 1
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
}
Solution 2
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).
Solution 3
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
Solution 4
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
}
Solution 5
Setting the gestureRecognizers delegate to a viewController as below no longer work on ios6
for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
gR.delegate = self;
}
Rich86man
Updated on February 05, 2020Comments
-
Rich86man about 4 years
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.
-
isaac over 12 yearsSataym, are you suggesting that it's impossible to place buttons or interactive elements in a UIPageViewController? And Apple just forgot to mention that in all the UIPageViewController documentation?
-
Satyam over 12 yearsNo Look at Philips answer. Until and unless we comment that line of code, none of the controls will receive touch events unless it is first view. After commenting that line of code, it can be any custom button or label or UIButton or something else will receive touch events.
-
SpaceTrucker over 12 yearsI've ran in this too. I do have a button in the bottom middle of the page. That seems to work. but putting it on the left or right won't.
-
Giorgio Barchiesi over 12 yearsThank you, this solved my problem. In my case I wanted the tapping gesture to be in my full control, regardless of the touch position, so the code was even simpler: it just checks whether the gestureRecognizer is kind of class UITapGestureRecognizer.
-
DonnaLea almost 12 yearsI like that this method doesn't mess with setting the delegate of the gesture recognizers.
-
Kof almost 12 yearsThere seems to be a problem with this solution, at least with my case. Once I've changed the delegate of the gesture recognizer, I started getting
NSInvalidArgumentException
every timeviewControllerBeforeViewController
orviewControllerAfterViewController
returned nil (indicated last page). Exception reason:The number of view controllers provided (0) doesn't match the number required (2) for the requested transition
. -
Chintan Patel over 11 yearsMuch better solution. However, i ended up enabling and disabling tapRecognizer as i again needed to enable the tap gesture after a sub view is dismissed.
-
Basem Saadawy over 11 yearsVery simple. I prefer it. Thank you.
-
Rob over 11 yearsIsn't the whole point of responder chains that you can have handlers and they can do something or not? That you don't have to just have one way to do something? I'd like to see a solution where the tap on the far right of the page is ignored by the controls (or toolbar), and sent up the chain so it can still trigger paging. [That's why responders do Chain of Responsibility; this solution just sets that design aside.]
-
DiscDev about 11 yearsGreat solution and easy to understand. This should be the accepted answer. Note that this was only an issue for my users on iOS 5.x. On 6.x the buttons on the right side of the UIPageViewController received touch events normally and did not advance to the next page. Once I added this code my buttons worked properly on all iOS versions that I support (> 5.0)
-
amergin almost 11 years@Kof the NSInvalidArgumentExceptions you're getting seem to occur only on iOS 6 when using the pagecurl transition. It can be avoided by returning [self viewControllerAtIndex:0] instead of nil (for viewControllerBeforeViewController) and [self viewControllerAtIndex:maxpages] instead of nil (for viewControllerAfterViewController) - an unfortunate side effect is the additional page curl animation.
-
Septiadi Agus over 10 yearswhat template? What template are you talking about?
-
Septiadi Agus over 10 yearsself.pageViewController.gestureRecognizers is empty if we uses UIPageViewControllerTransitionStyleScroll
-
Pat McG over 10 yearsThe template in question is the RootViewController class generated by Xcode if you create a new Page-Based Application.
-
beryllium over 10 yearsIn iOS6
_pageViewController.gestureRecognizers
(scroll effect) is empty array and you can't handle those recognizers. -
Bill Cheswick almost 10 yearsI create pageviewcontrollers regularly as my user jumps, curls, and slides to various different page views. In the routine that creates a new pageviewcontroller, I use a slightly simpler version of the excellent code shown above:
-
Bill Cheswick almost 10 yearsI create pageviewcontrollers regularly as my user jumps, curls, and slides to various different page views. In the routine that creates a new pageviewcontroller, I use a slightly simpler version of the excellent code shown above:- (UIPageViewController *) newPageController: (PageVC *)newPageVC {
-
Asela Liyanage over 4 yearsDidn't match a gesture for me on iOS 12, Swift 4.2
-
David over 4 yearsThis is the simplest solution, but instead of enabled use isUserInteractionEnabled