UIPageViewController returns no Gesture Recognizers in iOS 6

偶尔善良 提交于 2019-12-17 06:34:12

问题


I am trying to disable the pan gesture recognizer for a UIPageViewController.

On iOS 5 I can loop through them and disable them.

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

On iOS 6 using UIPageViewControllerTransitionStyleScroll there are no gesture recognizers returned by the Page View Controller.

Clarification

This can be boiled down to:

self.pageViewController.gestureRecognizers = 0 when UIPageViewController's transition style is set to scroll so I can't access the gesture recognizers.

Is there any way I can get around this? I don't think I am doing anything wrong since the curl transition works fine.


回答1:


There is a bug filed in radar for this behavior. So, I bet that until Apple fixes it there will be no chance to solve this.

One workaround that comes to my mind is laying a transparent subview on top of your UIPageViewController and add to it a UIPanGestureRecognizer to intercept that kind of gesture and not forward further. You could enable this view/recognizer when disabling the gesture is required.

I tried it with a combination of Pan and Tap gesture recognizers and it works.

This is my test code:

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

}

When g2 is enabled, it will prevent g1 from being recognized. On the other hand, it will not prevent s1 from being recognized.

I understand this is hack, but in the face of a seeming bug in UIPageViewController (at least, actual behavior is blatantly different from what the reference states), I cannot see any better solution.




回答2:


Found this in UIPageViewController.h:

// Only populated if transition style is 'UIPageViewControllerTransitionStylePageCurl'. @property(nonatomic, readonly) NSArray *gestureRecognizers;

So, not a bug - by design the pageViewController doesn't get gesture recognizers when scroll style is set.




回答3:


You can always try to disable user interaction on the page view controller's sub view:

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



回答4:


According to the UIPageViewController header file, when the datasource is nil, gesture-driven navigation is disabled.

So, set datasource to nil when you want to disable swiping, then when you want to enable swiping, reset the data source.

i.e.

// turns off paging
pageViewController.datasource = nil

// turns on paging
pageViewController.datasource = self;



回答5:


You can access the UIPanGestureRecognizer via the UIScrollview from the UIPageViewController.

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



回答6:


If the UIPageViewControllers UIPanGestureRecognizer is eating / swallowing all events away from an other PanGestureRecognizer (eg. a sliding menu).

You can easily expand on Bernies solution and make the UIScrollViews PanGestureRecognizer require the other Recognizer to fail. Something like this:

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

        [scrollView.panGestureRecognizer requireGestureRecognizerToFail:otherRecognizer];
    }
}

This way the scrolling PanGestureRecognizer only fires in the areas I intended him to.

This might not be the best future prove solution since we require Apple not to change the UIPageViewControllers internal use of an UIScrollView. but...




回答7:


Assuming you can find the gesture recognizers and remove them is very brittle. You are using assumed knowledge of how Apple uses implements UIPageViewController to provide functionality in your app. If this changes (such as between iOS 5 and iOS 6) then you're code app will start to behave in unexpected ways. It's almost like using a private API - you've no guarantee it will work with the next OS release.




回答8:


What happens if you use KVC (Key Value Coding) methods to access the recognizers? (I'm not where I can test this at the moment.)

A quick test might be to retrieve the number of gestureRecognizers:

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

If that works, you might go further to retrieve the array of recognizers:

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

Thin, but maybe...

Edit: was able to finally test. Used the following:

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

It didn't make any difference. The curl style worked fine both ways; the scroll style showed the arrays as not nil, but empty. An interesting thing, though, was that the view ALSO did not give up its recognizers - though the scroll functionality is there, so it must have at least a pan recognizer...




回答9:


Another solution, just for history. You can make any view to be some gesture recognizer breaker and it should work in that view's rectangle. There must be another UIPanGestureRecognizer with delegate. It can be any object with one method:

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

You can use following class for recognizer breaking purposes:

@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

This is works for example as singletone:

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

After calling -lockForView: page controller's gesture doesn't work on dragging in view's frame. For example I want to lock whole space of my view controller. So at some point in view controller method I call

[PageViewControllerLocker() lockForView:self.view];

And at another point

[PageViewControllerLocker() unlockForView:self.view];



回答10:


Inside the pageviewcontroller there is a subview called quieingscrollview it has 3 gesture recognizers which controls the page view controller to get them use

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



回答11:


In my case, I had a UIView "Info Panel" that came down over my UIPageViewController, but the page view controller's gesture recognizers interfered with the navigation through my info panel.

My solution was to set the dataSource to nil, but also to not allow the page view controller to update focus while the info panel is up:

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



回答12:


You could use the delegate methods of UIGestureRecognizer to catch and disable any gestures For example, you could use this delegate callback:gestureRecognizer:shouldReceiveTouch:. Just make sure to set the delegate for all the recognizers.



来源:https://stackoverflow.com/questions/13103613/uipageviewcontroller-returns-no-gesture-recognizers-in-ios-6

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!