UIPageViewController在iOS 6中不返回Gesture Recognizers

时间:2012-10-27 19:50:29

标签: iphone objective-c uikit uipageviewcontroller

我正在尝试为UIPageViewController禁用平移手势识别器。

在iOS 5上,我可以遍历它们并禁用它们。

for (UIGestureRecognizer* recognizer in self.pageViewController.gestureRecognizers) {
    if ([recognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        recognizer.enabled = NO;
    }
}

在使用UIPageViewControllerTransitionStyleScroll的iOS 6上,页面视图控制器没有返回任何手势识别器。

澄清

这可归结为:

当UIPageViewController的过渡样式设置为滚动时,

self.pageViewController.gestureRecognizers = 0,因此我无法访问手势识别器。

有什么方法可以解决这个问题吗?我不认为我做错了什么,因为卷曲过渡工作正常。

12 个答案:

答案 0 :(得分:31)

在UIPageViewController.h中找到它:

  

//仅在转换样式为'UIPageViewControllerTransitionStylePageCurl'时才填充。   @property(非原子,只读)NSArray * gestureRecognizers;

所以,不是错误 - 按设计,当设置滚动样式时,pageViewController不会获得手势识别器。

答案 1 :(得分:27)

您始终可以尝试在页面视图控制器的子视图中禁用用户交互:

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

答案 2 :(得分:25)

此行为有bug filed in radar。所以,我敢打赌,在Apple修复之前,没有机会解决这个问题。

我想到的一个解决方法是在UIPageViewController的顶部放置一个透明的子视图,并添加UIPanGestureRecognizer来拦截这种姿势,而不是进一步前进。您可以在需要禁用手势时启用此视图/识别器。

我尝试使用Pan和Tap手势识别器的组合,它可以工作。

这是我的测试代码:

- (void)viewDidLoad {
  [super viewDidLoad];

   UIPanGestureRecognizer* g1 = [[[UIPanGestureRecognizer alloc] initWithTarget:self
                                                                      action:@selector(g1Pan:)] autorelease];
  [self.view addGestureRecognizer:g1];

  UITapGestureRecognizer* s1 = [[[UITapGestureRecognizer alloc] initWithTarget:self
                                                                      action:@selector(g1Tap:)] autorelease];

  [self.view addGestureRecognizer:s1];

  UIView* anotherView = [[[UIView alloc]initWithFrame:self.view.bounds] autorelease];
  [self.view addSubview:anotherView];

  UIPanGestureRecognizer* g2 = [[[UIPanGestureRecognizer alloc] initWithTarget:self
                                                                      action:@selector(g2Pan:)] autorelease];
  [anotherView addGestureRecognizer:g2];

}

启用g2后,它将阻止g1被识别。另一方面,它不会阻止s1被识别。

我理解这是黑客,但面对UIPageViewController中的一个看似错误(至少,实际行为与参考状态明显不同),我看不出更好的解决方案。

答案 3 :(得分:18)

根据UIPageViewController头文件,当数据源为nil时,禁用手势驱动导航。

因此,如果要禁用滑动,请将datasource设置为nil,然后当您要启用滑动时,请重置数据源。

// turns off paging
pageViewController.datasource = nil

// turns on paging
pageViewController.datasource = self;

答案 4 :(得分:8)

您可以从UIPageViewController通过UIScrollview访问UIPanGestureRecognizer。

for (UIView *view in self.pageController.view.subviews) {
    if ([view isKindOfClass:[UIScrollView class]])
    {
        UIScrollView *scrollView = (UIScrollView *)view;

        UIPanGestureRecognizer* panGestureRecognizer = scrollView.panGestureRecognizer;
        [panGestureRecognizer addTarget:self action:@selector(move:)];
    }
}

答案 5 :(得分:2)

如果UIPageViewControllers UIPanGestureRecognizer正在进食/吞咽远离其他PanGestureRecognizer的所有事件(例如滑动菜单)。

您可以轻松扩展Bernies解决方案并使UIScrollViews PanGestureRecognizer要求其他识别器失败。 像这样:

for (UIView *view in pageViewController.view.subviews) {
    if ([view isKindOfClass:[UIScrollView class]]){
        UIScrollView *scrollView = (UIScrollView *)view;

        [scrollView.panGestureRecognizer requireGestureRecognizerToFail:otherRecognizer];
    }
}

这样滚动的PanGestureRecognizer只会在我打算进入的区域触发。

这可能不是最好的未来证明解决方案,因为我们要求Apple不要更改UIPcViewView控制器内部使用UIScrollView。但...

答案 6 :(得分:1)

假设您可以找到手势识别器并将其移除是非常脆弱的。您正在使用Apple的假设知识实现UIPageViewController如何在您的应用程序中提供功能。如果这种情况发生变化(例如在iOS 5和iOS 6之间),那么您的代码应用程序将开始以意想不到的方式运行。这几乎就像使用私有API一样 - 您无法保证它将适用于下一个操作系统版本。

答案 7 :(得分:1)

如果使用KVC(键值编码)方法访问识别器会发生什么? (我现在不是我可以测试它的地方。)

快速测试可能是检索gestureRecognizers的数量:

[self.pageViewController countOfKey:@"gestureRecognizers"];

如果可行,您可以进一步检索识别器数组:

NSArray *recognizers = [self.pageViewController
                        mutableArrayValueForKey:@"gestureRecognizers"];

很薄,但也许......

编辑:能够最终测试。使用以下内容:

NSArray *pvcGRsNoKVC = [[NSArray alloc]
                       initWithArray:self.pageViewController.gestureRecognizers];
NSArray *viewGRsNoKVC = [[NSArray alloc] initWithArray:self.view.gestureRecognizers];

NSArray *pvcGRsKVC = [[NSArray alloc]
                     initWithArray:[self.pageViewController valueForKey:@"gestureRecognizers"]];
NSArray *viewGRsKVC = [[NSArray alloc]
                      initWithArray:[self.view valueForKey:@"gestureRecognizers"]];

没有任何区别。卷曲风格两种方式都很好;滚动样式显示数组不是nil,而是空的。但有趣的是,视图也没有放弃它的识别器 - 虽然滚动功能在那里,所以它必须至少有一个泛识别器......

答案 8 :(得分:0)

另一种解决方案,仅限历史。 你可以使任何视图成为一些手势识别器断路器,它应该在该视图的矩形中工作。必须有另一个UIPanGestureRecognizer与委托。它可以是一种方法的任何对象:

static UIPageViewController* getPageViewControllerFromView(UIView* v) {
    UIResponder* r = v.nextResponder;
    while (r) {
        if ([r isKindOfClass:UIPageViewController.class])
            return (UIPageViewController*)r;
        r = r.nextResponder;
    }
    return nil;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    if (getPageViewControllerFromView(otherGestureRecognizer.view))
    {
        otherGestureRecognizer.enabled = NO;
        otherGestureRecognizer.enabled = YES;
    }
    return NO;
}

您可以使用以下类来识别破坏程序:

@interface GestureRecognizerBreaker : NSObject <UIGestureRecognizerDelegate>
{
    UIGestureRecognizer* breaker_;
    BOOL(^needsBreak_)(UIGestureRecognizer*);
}

- (id) initWithBreakerClass:(Class)recognizerClass
                    checker:(BOOL(^)(UIGestureRecognizer* recognizer))needsBreak;

- (void) lockForView:(UIView*)view;
- (void) unlockForView:(UIView*)view;
@end

@implementation GestureRecognizerBreaker
- (void) dummy:(id)r {}
- (id) initWithBreakerClass:(Class)recognizerClass checker:(BOOL(^)(UIGestureRecognizer*))needsBreak {
    self = [super init];
    if (!self)
        return nil;
    NSParameterAssert([recognizerClass isSubclassOfClass:UIGestureRecognizer.class] && needsBreak);
    needsBreak_ = needsBreak;
    breaker_ = [[recognizerClass alloc] initWithTarget:self action:@selector(dummy:)];
    breaker_.delegate = self;
    return self;
}

- (BOOL) gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
    if (needsBreak_(otherGestureRecognizer)) {
        otherGestureRecognizer.enabled = NO;
        otherGestureRecognizer.enabled = YES;
    }
    return NO;
}

- (void) lockForView:(UIView*)view {
    [view addGestureRecognizer:breaker_];
}

- (void) unlockForView:(UIView*)view {
    [view removeGestureRecognizer:breaker_];
}
@end

这可以作为单身人士使用:

static GestureRecognizerBreaker* PageViewControllerLocker() {
    static GestureRecognizerBreaker* i = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        i = [[GestureRecognizerBreaker alloc]
            initWithBreakerClass:UIPanGestureRecognizer.class
            checker:^BOOL(UIGestureRecognizer* recognizer) {
                UIView* v = recognizer.view;
                UIResponder* r = v.nextResponder;
                while (r) {
                    if ([r isKindOfClass:UIPageViewController.class])
                        return YES;
                    r = r.nextResponder;
                }
                return NO;
            }];
    });
    return i;
}

调用-lockForView后:页面控制器的手势在视图框架中拖动时不起作用。例如,我想锁定视图控制器的整个空间。所以在视图控制器方法的某个时刻我调用

[PageViewControllerLocker() lockForView:self.view];

另一点

[PageViewControllerLocker() unlockForView:self.view];

答案 9 :(得分:0)

在pageviewcontroller内部有一个名为quieingscrollview的子视图,它有3个手势识别器,用于控制页面视图控制器 让他们使用

[[pageViewController.view.subviews objectAtIndex:0] gestureRecognizers]

答案 10 :(得分:0)

在我的情况下,我有一个UIView“信息面板”,它来自我的UIPageViewController,但页面视图控制器的手势识别器干扰了我的信息面板导航。

我的解决方案是将dataSource设置为nil,但也不允许页面视图控制器在信息面板启动时更新焦点:

- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
{
    if (self.infoPanel) {
        return NO;
    } else {
        return YES;
    }
}

答案 11 :(得分:-1)

您可以使用UIGestureRecognizer的委托方法来捕获和禁用任何手势例如,您可以使用此委托回调:gestureRecognizer:shouldReceiveTouch:。只需确保为所有识别器设置委托。