UIPageViewController手势识别器

56

我有一个UIPageViewController加载我的ViewController。

这些视图控制器上有按钮,这些按钮被PageViewControllers手势识别器覆盖。

例如,我在视图控制器的右侧有一个按钮,当您按下按钮时,PageViewController接管并更改页面。

如何使按钮接收触摸并取消PageViewController中的手势识别器?

我认为PageViewController使我的ViewController成为其视图的子视图。

我知道我可以关闭所有手势,但这不是我要的效果。

最好不要子类化PageViewController,因为苹果说这个类不应该被子类化。


我一直在开发一个使用PageViewController的应用程序... 我正在寻找一种编程方式来改变页面... 你能帮我一下吗? - Toncean Cosmin
2
要通过编程方式更改页面,您需要执行以下操作:NSArray *viewControllers = [NSArray arrayWithObject:pageIWantToTurnTo];然后[pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL]; - Rich86man
有没有任何示例,其中完成不是NULL? - Bagusflyer
@Rich86man,你能告诉我如何在页面视图控制器中显示多个视图控制器吗?我正在使用UIPageViewController,但它会重复相同的视图。你能帮帮我吗? - iPhone Programmatically
FYI - 在iOS 6.x上,这似乎不是一个问题。我在UIPageViewController的右侧有按钮,它们可以正常接收点击。但在iOS 5.1.1上,用户抱怨点击右侧的按钮会翻页,就像这里描述的问题一样。 - DiscDev
显示剩余3条评论
15个回答

56

这是另一种解决方案,可以在Xcode模板中的viewDidLoad模板右侧添加,在self.view.gestureRecognizers = self.pageViewController.gestureRecognizers部分之后。它避免了干扰手势识别器的内部或处理其代理的麻烦。它只是从视图中删除了点击手势识别器,仅保留了滑动手势识别器。

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];
}

现在要切换页面,你需要滑动。点击现在仅适用于页面视图顶部的控件(这正是我想要的)。


2
我喜欢这种方法不会干扰手势识别器的委托设置。 - DonnaLea
很棒的解决方案,易于理解。这应该是被接受的答案。请注意,这只是我的iOS 5.x用户遇到的问题。在6.x上,UIPageViewController右侧的按钮正常接收触摸事件,并且不会跳转到下一页。一旦我添加了这段代码,我的按钮在我支持的所有iOS版本(> 5.0)上都能正常工作。 - DiscDev
什么模板?你在说哪个模板? - Septiadi Agus
12
如果我们使用UIPageViewControllerTransitionStyleScroll,self.pageViewController.gestureRecognizers将为空。 - Septiadi Agus
所涉及的模板是由Xcode生成的RootViewController类,如果您创建一个新的基于页面的应用程序。 - Pat McG
显示剩余5条评论

50

您可以覆盖

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

为了更好地控制 PageViewController 何时接收触摸事件,可以查看“防止手势识别器分析触摸事件”中的内容。请参考Dev API Gesture Recognizers

在 RootViewController 中,我的解决方案如下:

在 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;
}

覆盖:

-(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;
}

别忘了将RootViewController设置为UIGestureRecognizerDelegate。

(顺便说一句,我只处于横屏模式。)

编辑 - 上面的代码转换成Swift 2:

在viewDidLoad中:

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

使页面视图控制器继承 UIGestureRecognizerDelegate,然后添加:

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
}

谢谢,这解决了我的问题。在我的情况下,我希望轻拍手势完全受控制,而不管触摸位置如何,因此代码更简单:它只检查gestureRecognizer是否属于UITapGestureRecognizer类。 - Giorgio Barchiesi
13
这个解决方案似乎存在问题,至少对于我的情况是这样。一旦我改变了手势识别器的委托,每次viewControllerBeforeViewControllerviewControllerAfterViewController返回nil时,我都会收到'NSInvalidArgumentException'。异常原因是:“提供的视图控制器数(0)与所需的过渡所需的数量(2)不匹配”。 - Kof
@K,您所遇到的NSInvalidArgumentException异常似乎只会在使用页面翻页转换时在iOS 6上出现。可以通过返回[self viewControllerAtIndex:0]而不是nil(对于viewControllerBeforeViewController)和[self viewControllerAtIndex:maxpages]而不是nil(对于viewControllerAfterViewController)来避免此问题——不幸的是,这会带来额外的页面翻页动画。 - amergin
17
在iOS6中,_pageViewController.gestureRecognizers(滚动效果)是一个空数组,你无法处理这些识别器。 - beryllium

4
我遇到了同样的问题。示例和文档在loadView或viewDidLoad中执行以下操作:
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

这将UIViewControllers视图中的手势识别器替换为UIPageViewController的手势识别器。现在,当发生触摸时,它们首先通过pageViewControllers手势识别器发送 - 如果不匹配,则发送到子视图。
只需取消注释该行,一切都按预期工作。
菲利普

3
在更新的版本(我使用的是针对iOS 8.1+ 的Xcode 7.3),这些解决方案都似乎不起作用。
接受的答案会导致错误崩溃:
UIScrollView的内置pan手势识别器必须将其滚动视图设置为其委托。
目前排名最高的答案(来自Pat McG)也不再起作用,因为UIPageViewController的滚动视图似乎正在使用无法检查的手势识别器子类。 因此,语句if([recognizer isKindOfClass:[UITapGestureRecognizer class]])永远不会执行。
我选择只需将每个手势识别器上的cancelsTouchesInView设置为false,这样UIPageViewController的子视图也可以接收触摸事件。
在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
}

3

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

允许点击穿透到 UIPageViewController 内部的按钮。


2

在我的情况下,我想禁用UIPageControl上的轻触,并让另一个按钮接收轻触。滑动仍然有效。我尝试了很多方法,但我相信这是最简单有效的解决方案:

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

这是从UIPageController子视图中获取UIPageControl视图并禁用用户交互。

这是最简单的解决方案,但是不要使用“enabled”,而是使用“isUserInteractionEnabled”。 - David

1

只需在RootViewController中创建一个子视图(与新IBOutlet gesturesView相关联),并将手势分配给此新视图。此视图覆盖您希望启用手势的屏幕部分。

在viewDidLoad中更改:

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

到:

self.gesturesView.gestureRecognizers = self.pageViewController.gestureRecognizers;

0

还可以使用这个(感谢帮助,关于委托的说法):

// add UIGestureRecognizerDelegate

NSPredicate *tp = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", [UITapGestureRecognizer class]];
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)[self.pageViewController.view.gestureRecognizers filteredArrayUsingPredicate:tp][0];
tgr.delegate = self; // tap delegating

NSPredicate *pp = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", [UIPanGestureRecognizer class]];
UIPanGestureRecognizer *pgr = (UIPanGestureRecognizer *)[self.pageViewController.view.gestureRecognizers filteredArrayUsingPredicate:pp][0];
pgr.delegate = self; // pan delegating

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 
{
    CGPoint touchPoint = [touch locationInView:self.view];

    if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) && touchPoint.y > 915 ) {
        return NO; // if y > 915 px in portrait mode
    }

    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) && touchPoint.y > 680 ) {
        return NO; // if y > 680 px in landscape mode
    }

    return YES;
}

对我来说完美地工作了 :)


0

我经常创建页面视图控制器,因为我的用户会跳跃、卷曲和滑动到不同的页面视图。在创建新的页面视图控制器的例程中,我使用了上面优秀代码的稍微简化版本:

UIPageViewController *npVC = [[UIPageViewController alloc]
                       initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
                       navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                       options: options];
...

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

现在,我的选项卡按照我的意愿工作(左右选项卡可以跳转页面,而且卷曲效果也很好)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接